How do you treat hashes in arrays properly? - perl

I've got an array of hashes:
my #questions = (
{"Why do you study here?" => "bla"},
{"What are your hobbies?" => "blabla"});
And I try to loop through it:
foreach (#questions) {
my $key = (keys $_)[0];
$content .= "\\section{$key}\n\n$_{$key}\n\n";
}
giving me
Use of uninitialized value in concatenation (.) or string at
convert.pl line 44.
Where does the error come from?

$_{$key} looks up $key in the hash variable %_. The sigil $ at the beginning indicates that the type of the result is a scalar. It's the syntactic construct VAR{KEY} that determines that VAR must be a hash. Although $_ and %_ use the same symbol as a name, the different sigils make them unrelated variables.
You need to dereference the hash reference $_ into the underlying hash. The syntax for this is $_->{$key} or ${$_}{$key}.
See the reference tutorial for a more general presentation of the topic.

Gilles already explained how to use your current data structure, but I would recommend that you use a different data structure altogether: a simple hash.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
my %answers = (
"Why do you study here?" => "bla",
"What are your hobbies?" => "blabla"
);
while (my ($question, $answer) = each %answers) {
say "Question: $question";
say "Answer: $answer";
}
Output:
Question: Why do you study here?
Answer: bla
Question: What are your hobbies?
Answer: blabla
I find this easier to work with than an array of hashes, each of which only contains a single key/value pair.
If you want to iterate through the hash in a certain (non-sorted) order, there are a couple of options. The simplistic solution is to maintain an array of keys in the order you want to access them:
# In the order you want to access them
my #questions = ("What are your hobbies?", "Why do you study here?");
my %answers;
#answers{#questions} = ("blabla", "bla");
foreach my $question (#questions) {
say "Question: $question";
say "Answer: $answers{$question}";
}
Output:
Question: What are your hobbies?
Answer: blabla
Question: Why do you study here?
Answer: bla
Another option is to use Tie::IxHash (or the faster XS module Tie::Hash::Indexed) to access keys in insertion order:
use Tie::IxHash;
tie my %answers, "Tie::IxHash";
%answers = (
"Why do you study here?" => "bla",
"What are your hobbies?" => "blabla"
);
while (my ($question, $answer) = each %answers) {
say "Question: $question";
say "Answer: $answer";
}
Output:
Question: Why do you study here?
Answer: bla
Question: What are your hobbies?
Answer: blabla

The elements of #questions are references to hash, not hashes. Therefore, you should use them like this:
foreach (#questions) {
my $key = (keys %$_)[0];
print "\\section{$key}\n\n$_->{$key}\n\n";
}
See perlref for how to create and use reference.

Related

In Perl, how can I use the contents of a variable as the name of a hash? [duplicate]

This question already has answers here:
How can I use a variable as a variable name in Perl?
(3 answers)
Closed 6 years ago.
The code below works only with no strict.
Can anyone suggest a better way to do it?
%hash5=('key5', 5);
my $hash_name = 'hash5';
print $$hash_name{'key5'}, "\n";
Again my aim: I do not know the hash name. I only know, it is store in
the variable $hash_name. People have been suggesting things like:
my $hashref = \%hashABC;
which requires me to know that the hash name is '%hashABC'.
Using this example just above i would like to do sth like:
my $hash_name = 'hashABC';
my $hashref = \%$hash_name; # not possible, hope u get the aim
now i do not need to know the name of the hash anymore.
That is what i want.
Thx a lot guys!
(perl 5)
Instead of referring to the hash by name, use references.
# Here is our hash
my %hash = (key => 5);
# we make a reference to the hash
# this is like remembering the name of the variable, but safe
my $hashref = \%hash;
# here are two ways to access values in the referenced hash
say $$hashref{key};
say $hashref->{key}; # prefer this
Alternatively, keep a hash of hashes so that you can look up items by name:
# here is our hash again
my %hash = (key => 5);
# and here is a hash that maps names to hash references
my %hash_by_name;
# we remember the %hash as "hash5"
$hash_by_name{hash5} = \%hash;
# now we can access an element in that hash
say $hash_by_name{hash5}{key};
# we can also have a variable with the name:
my $name = "hash5";
say $hash_by_name{$name}{key};
Learn more about references in perlreftut.
In this case, disable strict temporally looks like the best solution, you can do it like this
#!/usr/bin/perl
use strict;
use warnings;
our %hash5=('key5', 5);
my $hash_name = 'hash5';
my $hash_ref;
{
no strict "refs";
$hash_ref = \%$hash_name;
}
print $hash_ref->{key5}, "\n";
Note: For this to work, %hash5 must be a global variable.
I don't know where the data in %hash_name come from. Did you read and store them in %hash_name? If so, perhaps a simpler solution would be to modify your program to read into a hash of hashes (as many have suggested), instead of reading into a global variable:
my %data = (
hash_name => get_data(),
);
# and then ...
my $name = get_name(); # e.g., 'hash_name'
my $data = $data{ $name } or do {
# error handling
...
};
Remember that the limitations that use strict imposes do not apply at all to the keys of a hash :-)

Dereferencing hashes of hashes in Perl

I'm trying to collect the values that I store in a hash of hashes, but I'm kinda confused in how perl does that. So, I create my hash of hashes as follows:
my %hash;
my #items;
#... some code missing here, generally I'm just populating the #items list
#with $currentitem items
while (<FILE>) { #read the file
($a, $b) = split(/\s+/,$_,-1);
$hash{$currentitem} => {$a => $b};
print $hash{$currentitem}{$a} . "\n";#this is a test and it works
}
The above code seems to work. Now, to the point: I have an array #items, which keeps the $currentitem values. And I want to do something like this:
#test = keys %hash{ $items[$num] };
So that I can get all the key/value pairs for a specific item. I've tried the line of code above, as well as
while ( ($key, $value) = each( $hash{$items[$num]} ) ) {
print "$key, $value\n";
}
I've even tried to populate the hash as follows:
$host{ "$currentcustomer" }{"$a"} => "$b";
Which seems to be more correct according to the various online sources I've met. But still, I can't access the data inside that hash... Any ideas?
I am confused by you saying that this works:
$hash{$currentitem} => {$a => $b};
That shouldn't work (and doesn't work for me). The => operator is a special kind of comma, not an assignment (see perlop). In addition, the construct on the right makes a new anonymous hash. Using that, a new anonymous hash would overwrite the old one for each element you tried to add. You would only ever have one element for each $currentitem.
Here is what you want for assignment:
$hash{$currentitem}{$a} = $b;
And here is how to get the keys:
keys %{ $hash{ $items[$num] } };
I suggest reading up on Perl references to get a better handle on this. The syntax can be a bit tricky at first.
Long answer is in perldoc perldsc.
Short answer is:
keys %{ $expr_producing_hash_ref };
In your case I believe it's
keys %{ $hash{$items[$num]} };

How do I create a hash of hashes in Perl?

Based on my current understanding of hashes in Perl, I would expect this code to print "hello world." It instead prints nothing.
%a=();
%b=();
$b{str} = "hello";
$a{1}=%b;
$b=();
$b{str} = "world";
$a{2}=%b;
print "$a{1}{str} $a{2}{str}";
I assume that a hash is just like an array, so why can't I make a hash contain another?
You should always use "use strict;" in your program.
Use references and anonymous hashes.
use strict;use warnings;
my %a;
my %b;
$b{str} = "hello";
$a{1}={%b};
%b=();
$b{str} = "world";
$a{2}={%b};
print "$a{1}{str} $a{2}{str}";
{%b} creates reference to copy of hash %b. You need copy here because you empty it later.
Hashes of hashes are tricky to get right the first time. In this case
$a{1} = { %b };
...
$a{2} = { %b };
will get you where you want to go.
See perldoc perllol for the gory details about two-dimensional data structures in Perl.
Short answer: hash keys can only be associated with a scalar, not a hash. To do what you want, you need to use references.
Rather than re-hash (heh) how to create multi-level data structures, I suggest you read perlreftut. perlref is more complete, but it's a bit overwhelming at first.
Mike, Alexandr's is the right answer.
Also a tip. If you are just learning hashes perl has a module called Data::Dumper that can pretty-print your data structures for you, which is really handy when you'd like to check what values your data structures have.
use Data::Dumper;
print Dumper(\%a);
when you print this it shows
$VAR1 = {
'1' => {
'str' => 'hello'
},
'2' => {
'str' => 'world'
}
};
Perl likes to flatten your data structures. That's often a good thing...for example, (#options, "another option", "yet another") ends up as one list.
If you really mean to have one structure inside another, the inner structure needs to be a reference. Like so:
%a{1} = { %b };
The braces denote a hash, which you're filling with values from %b, and getting back as a reference rather than a straight hash.
You could also say
$a{1} = \%b;
but that makes changes to %b change $a{1} as well.
I needed to create 1000 employees records for testing a T&A system. The employee records were stored in a hash where the key was the employee's identity number, and the value was a hash of their name, date of birth, and date of hire etc. Here's how...
# declare an empty hash
%employees = ();
# add each employee to the hash
$employees{$identity} = {gender=>$gender, forename=>$forename, surname=>$surname, dob=>$dob, doh=>$doh};
# dump the hash as CSV
foreach $identity ( keys %employees ){
print "$identity,$employees{$identity}{forename},$employees{$identity}{surname}\n";
}

Is %$var dereferencing a Perl hash?

I'm sending a subroutine a hash, and fetching it with my($arg_ref) = #_;
But what exactly is %$arg_ref? Is %$ dereferencing the hash?
$arg_ref is a scalar since it uses the $ sigil. Presumably, it holds a hash reference. So yes, %$arg_ref deferences that hash reference. Another way to write it is %{$arg_ref}. This makes the intent of the code a bit more clear, though more verbose.
To quote from perldata(1):
Scalar values are always named with '$', even when referring
to a scalar that is part of an array or a hash. The '$'
symbol works semantically like the English word "the" in
that it indicates a single value is expected.
$days # the simple scalar value "days"
$days[28] # the 29th element of array #days
$days{'Feb'} # the 'Feb' value from hash %days
$#days # the last index of array #days
So your example would be:
%$arg_ref # hash dereferenced from the value "arg_ref"
my($arg_ref) = #_; grabs the first item in the function's argument stack and places it in a local variable called $arg_ref. The caller is responsible for passing a hash reference. A more canonical way to write that is:
my $arg_ref = shift;
To create a hash reference you could start with a hash:
some_sub(\%hash);
Or you can create it with an anonymous hash reference:
some_sub({pi => 3.14, C => 4}); # Pi is a gross approximation.
Instead of dereferencing the entire hash like that, you can grab individual items with
$arg_ref->{key}
A good brief introduction to references (creating them and using them) in Perl is perldoc perfeftut. You can also read it online (or get it as a pdf). (It talks more about references in complex data structures than in terms of passing in and out of subroutines, but the syntax is the same.)
my %hash = ( fred => 'wilma',
barney => 'betty');
my $hashref = \%hash;
my $freds_wife = $hashref->{fred};
my %hash_copy = %$hash # or %{$hash} as noted above.
Soo, what's the point of the syntax flexibility? Let's try this:
my %flintstones = ( fred => { wife => 'wilma',
kids => ['pebbles'],
pets => ['dino'],
}
barney => { # etc ... }
);
Actually for deep data structures like this it's often more convenient to start with a ref:
my $flintstones = { fred => { wife => 'Wilma',
kids => ['Pebbles'],
pets => ['Dino'],
},
};
OK, so fred gets a new pet, 'Velociraptor'
push #{$flintstones->{fred}->{pets}}, 'Velociraptor';
How many pets does Fred have?
scalar # {flintstones->{fred}->{pets} }
Let's feed them ...
for my $pet ( # {flintstones->{fred}->{pets} } ) {
feed($pet)
}
and so on. The curly-bracket soup can look a bit daunting at first, but it becomes quite easy to deal with them in the end, so long as you're consistent in the way that you deal with them.
Since it's somewhat clear this construct is being used to provide a hash reference as a list of named arguments to a sub it should also be noted that this
sub foo {
my ($arg_ref) = #_;
# do something with $arg_ref->{keys}
}
may be overkill as opposed to just unpacking #_
sub bar {
my ($a, $b, $c) = #_;
return $c / ( $a * $b );
}
Depending on how complex the argument list is.

How do I do a simple Perl hash equivalence comparison?

I'm wondering if there's an idiomatic one-liner or a standard-distribution package/function that I can use to compare two Perl hashes with only builtin, non-blessed types. The hashes are not identical (they don't have equivalent memory addresses).
I'd like to know the answer for both for shallow hashes and hashes with nested collections, but I understand that shallow hashes may have a much simpler solution.
TIA!
Something like cmp_deeply available in Test::Deep ?
[This was a response to an answer by someone who deleted their answer.]
Uh oh!
%a ~~ %b && [sort values %a] ~~ [sort values %b]
doesn't check whether the values belong to the same keys.
#! perl
use warnings;
use strict;
my %a = (eat => "banana", say => "whu whu"); # monkey
my %b = (eat => "whu whu", say => "banana"); # gorilla
print "Magilla Gorilla is always right\n"
if %a ~~ %b && [sort values %a] ~~ [sort values %b];
I don't know if there's an easy way or a built-in package, and I don't know what happens when you just do %hash1 == %hash2 (but that's probably not it), but it's not terribly hard to roll your own:
sub hash_comp (\%\%) {
my %hash1 = %{ shift };
my %hash2 = %{ shift };
foreach (keys %hash1) {
return 1 unless defined $hash2{$_} and $hash1{$_} == $hash2{$_};
delete $hash1{$_};
delete $hash2{$_};
}
return 1 if keys $hash2;
return 0;
}
Untested, but should return 0 if the hashes have all the same elements and all the same values. This function will have to be modified to account for multidimensional hashes.
If you want something from a standard distribution, you could use Data::Dumper; and just dump the two hashes into two scalar variables, then compare the strings for equality. That might work.
There's also a package on CPAN called FreezeThaw that looks like it does what you want.
Note that to use the smart match (not repeated here because it's already posted), you will have to use feature; and it's only available for Perl 5.10. But who's still using Perl 5.8.8, right?
Thanks for your question.
I used Test::More::eq_hash as result.
hashes can be casted into arrays, where every value follows its key (but you won't know the order of the keys). So:
( join("",sort(%hash1)) eq join("",sort(%hash2)) )
Oh, wait, that won't work because there are some edge cases, like:
%hash1 = { 'aaa' => 'aa' };
%hash2 = { 'aa' => 'aaa' };
So it's best to use a character in the join() that you KNOW will never appear in any key or value. If the values are BLOBs, that will be a big problem, but for anything else you could use the NULL char "\0".
( join("\0",sort(%hash1)) eq join("\0",sort(%hash2)) )
Looks kinda ugly, I know, but it will do for checking if two hashes are equal in a shallow way, which is what most people are looking for.
For shallow hashes:
(grep {exists %hash2{$_}} keys %hash1) > 0
You can use eq_deeply in Test::Deep::NoTest. It just returns a boolean that you can check, without the extra overhead of the testing capabilities of the main module.
convert hashes to xml files and compare, and yes you could use multilevel.
sub isEqualHash
{
my ($self,$hash1, $hash2) = #_;
my $file1 = "c:/neo-file1.txt";
my $file2 = "c:/neo-file2.txt";
my $xmlObj = XML::Simple->new();
my $dummy_file = $xmlObj->XMLout($hash1,OutputFile => $file1);
my $dummy_file = $xmlObj->XMLout($hash2,OutputFile => $file2);
open FILE, "<".$file1;
my $file_contents1 = do { local $/; <FILE> };
close(FILE);
open FILE, "<".$file2;
my $file_contents2 = do { local $/; <FILE> };
close(FILE);
if($file_contents1 eq $file_contents2)
{
return "Passed";
}
else
{
return "Failed";
}
}