Perl Hash of Hash - perl

So I am trying to use a Perl HoH and push some values into an array from the HoH.
Here is a portion of the code to better explain;
my $hoh = (
antenna_included=>{
"1" => '1 MultiBand Antenna',
"2" =>'2 Multiband Antennas',
"3" =>'1 MultiBand Antenna & 2 WiFi Antennas',
"4" =>'2 Multiband Cellular Antennas & 2 WiFi Antennas',
"N" =>'No Antennas Included',
},
ip_rating=>{
I6 => 'IP 64',
CD => 'Intrinsically Safe, Class 1 Div 2, IP 64',
NI => 'No',
});
foreach $group ( sort keys %hoh ) {
foreach $spec ( sort keys %{ $hoh{$group} } ) {
print "$spec=>$hoh{$group}{$spec}\n";
}
print "what part is it: ";
my $input = <STDIN>;
chomp $input;
if ( exists $hoh{$group} ) {
print "$hoh{$spec}\n"; #this is the problematic line.
}
else {
print "not a match!\n";
}
}
Basically the goal of this script is to loop through the HoH, but throughout each block of hash it gives STDIN, then you type in the key, and then then I want to push the value of that element into an array. (Right now the code just says print for debugging).
I have tried
$hoh{$group}{$spec}
$hoh{$group}
$hoh{$group}->{$spec}
For $hoh{$group} I get HASH(0x6ff920) and all of the other values it is just blank, no error.
Any thoughts? Thank you

Use the -> operator.
Everything underneath the HoH is a hashref, and accessing elements of those requires the -> operator. (Technically, you have a "hash of hashref" not a "hash of hash," since such a thing isn't possible.)
See the perlref docs for details about references.
$hoh{$group}->{$spec}
UPDATE
Here's a modified version of your program. The differences are:
The initial declaration of %hoh is done with the hash variable %hoh, not the scalar variable, $hoh. Assigning the initializing list to $hoh would result in the last element of the list being assigned to the variable, in this case, the hashref corresponding to ip_rating. This is almost certainly not what you want.
Attempting to access an element of $hoh{$group} requires the -> operator, like `$hoh{$group}->{$spec}.
The problem area probably needs to be checking for the existence of $hoh{$group}->{$input}. If it doesn't exist, the input isn't valid, which is what the message suggests.
You just need to understand the difference between a hash and a hashref (similar to the difference between an array and an arrayref) and realize that hashes and arrays cannot be elements of other hashes or arrays. Hashrefs and arrayrefs can be elements of other data structures, because only scalars can be contained within another data structure. Hashes and arrays are not scalars, but references are scalars (including hashref, arrayrefs, coderefs, and even scalarrefs) and are necessary to create any sort of complex, nested data structure.
my %hoh = (
antenna_included=>{
"1" => '1 MultiBand Antenna',
"2" =>'2 Multiband Antennas',
"3" =>'1 MultiBand Antenna & 2 WiFi Antennas',
"4" =>'2 Multiband Cellular Antennas & 2 WiFi Antennas',
"N" =>'No Antennas Included',
},
ip_rating=>{
I6 => 'IP 64',
CD => 'Intrinsically Safe, Class 1 Div 2, IP 64',
NI => 'No',
});
foreach $group ( sort keys %hoh ) {
foreach $spec ( sort keys %{ $hoh{$group} } ) {
print "$spec=>$hoh{$group}->{$spec}\n";
}
print "what part is it: ";
my $input = <STDIN>;
chomp $input;
if ( exists $hoh{$group}->{$input} ) {
print $hoh{$group}->{$input}."\n";
}
else {
print "not a match!\n";
}
}

Braces {...} and parentheses (...) are very diffferent. The first defines an anonymous hash, which you would assign to a scalar variable, while the second defines a list, which you would assign to a hash variable. You have a mixture of the two, and it isn't clear which you meant.
If you have a scalar variable $hoh then you need to initialise it with
my $hoh = { ... }
and access the elements with the indirection operator
$hoh->{$group}->{$spec}
or
$hoh->{$group}{$spec}
If you have a hash variable then you need to initialise it with
my %hoh = ( ... )
and access the elements directly
$hoh{$group}->{$spec}
or
$hoh{$group}{$spec}

Related

Why I can use #list to call an array, but can't use %dict to call a hash in perl? [duplicate]

This question already has answers here:
Why do you need $ when accessing array and hash elements in Perl?
(9 answers)
Closed 8 years ago.
Today I start my perl journey, and now I'm exploring the data type.
My code looks like:
#list=(1,2,3,4,5);
%dict=(1,2,3,4,5);
print "$list[0]\n"; # using [ ] to wrap index
print "$dict{1}\n"; # using { } to wrap key
print "#list[2]\n";
print "%dict{2}\n";
it seems $ + var_name works for both array and hash, but # + var_name can be used to call an array, meanwhile % + var_name can't be used to call a hash.
Why?
#list[2] works because it is a slice of a list.
In Perl 5, a sigil indicates--in a non-technical sense--the context of your expression. Except from some of the non-standard behavior that slices have in a scalar context, the basic thought is that the sigil represents what you want to get out of the expression.
If you want a scalar out of a hash, it's $hash{key}.
If you want a scalar out of an array, it's $array[0]. However, Perl allows you to get slices of the aggregates. And that allows you to retrieve more than one value in a compact expression. Slices take a list of indexes. So,
#list = #hash{ qw<key1 key2> };
gives you a list of items from the hash. And,
#list2 = #list[0..3];
gives you the first four items from the array. --> For your case, #list[2] still has a "list" of indexes, it's just that list is the special case of a "list of one".
As scalar and list contexts were rather well defined, and there was no "hash context", it stayed pretty stable at $ for scalar and # for "lists" and until recently, Perl did not support addressing any variable with %. So neither %hash{#keys} nor %hash{key} had meaning. Now, however, you can dump out pairs of indexes with values by putting the % sigil on the front.
my %hash = qw<a 1 b 2>;
my #list = %hash{ qw<a b> }; # yields ( 'a', 1, 'b', 2 )
my #l2 = %list[0..2]; # yields ( 0, 'a', 1, '1', 2, 'b' )
So, I guess, if you have an older version of Perl, you can't, but if you have 5.20, you can.
But for a completist's sake, slices have a non-intuitive way that they work in a scalar context. Because the standard behavior of putting a list into a scalar context is to count the list, if a slice worked with that behavior:
( $item = #hash{ #keys } ) == scalar #keys;
Which would make the expression:
$item = #hash{ #keys };
no more valuable than:
scalar #keys;
So, Perl seems to treat it like the expression:
$s = ( $hash{$keys[0]}, $hash{$keys[1]}, ... , $hash{$keys[$#keys]} );
And when a comma-delimited list is evaluated in a scalar context, it assigns the last expression. So it really ends up that
$item = #hash{ #keys };
is no more valuable than:
$item = $hash{ $keys[-1] };
But it makes writing something like this:
$item = $hash{ source1(), source2(), #array3, $banana, ( map { "$_" } source4()};
slightly easier than writing:
$item = $hash{ [source1(), source2(), #array3, $banana, ( map { "$_" } source4()]->[-1] }
But only slightly.
Arrays are interpolated within double quotes, so you see the actual contents of the array printed.
On the other hand, %dict{1} works, but is not interpolated within double quotes. So, something like my %partial_dict = %dict{1,3} is valid and does what you expect i.e. %partial_dict will now have the value (1,2,3,4). But "%dict{1,3}" (in quotes) will still be printed as %dict{1,3}.
Perl Cookbook has some tips on printing hashes.

How can I find out if a hash has an odd number of elements in assignment?

How could I find out if this hash has an odd number of elements?
my %hash = ( 1, 2, 3, 4, 5 );
Ok, I should have written more information.
sub routine {
my ( $first, $hash_ref ) = #_;
if ( $hash_ref refers to a hash with odd numbers of elements ) {
"Second argument refers to a hash with odd numbers of elements.\nFalling back to default values";
$hash_ref = { option1 => 'office', option2 => 34, option3 => 'fast' };
}
...
...
}
routine( [ 'one', 'two', 'three' ], { option1 =>, option2 => undef, option3 => 'fast' );
Well, I suppose there is some terminological confusion in the question that should be clarified.
A hash in Perl always has the same number of keys and values - because it's fundamentally an engine to store some values by their keys. I mean, key-value pair should be considered as a single element here. )
But I guess that's not what was asked really. ) I suppose the OP tried to build a hash from a list (not an array - the difference is subtle, but it's still there), and got the warning.
So the point is to check the number of elements in the list which will be assigned to a hash. It can be done as simple as ...
my #list = ( ... there goes a list ... );
print #list % 2; # 1 if the list had an odd number of elements, 0 otherwise
Notice that % operator imposes the scalar context on the list variable: it's simple and elegant. )
UPDATE as I see, the problem is slightly different. Ok, let's talk about the example given, simplifying it a bit.
my $anhash = {
option1 =>,
option2 => undef,
option3 => 'fast'
};
See, => is just a syntax sugar; this assignment could be easily rewritten as...
my $anhash = {
'option1', , 'option2', undef, 'option3', 'fast'
};
The point is that missing value after the first comma and undef are not the same, as lists (any lists) are flattened automatically in Perl. undef can be a normal element of any list, but empty space will be just ignored.
Take note the warning you care about (if use warnings is set) will be raised before your procedure is called, if it's called with an invalid hash wrapped in reference. So whoever caused this should deal with it by himself, looking at his own code: fail early, they say. )
You want to use named arguments, but set some default values for missing ones? Use this technique:
sub test_sub {
my ($args_ref) = #_;
my $default_args_ref = {
option1 => 'xxx',
option2 => 'yyy',
};
$args_ref = { %$default_args_ref, %$args_ref, };
}
Then your test_sub might be called like this...
test_sub { option1 => 'zzz' };
... or even ...
test_sub {};
The simple answer is: You get a warning about it:
Odd number of elements in hash assignment at...
Assuming you have not been foolish and turned warnings off.
The hard answer is, once assignment to the hash has been done (and warning issued), it is not odd anymore. So you can't.
my %hash = (1,2,3,4,5);
use Data::Dumper;
print Dumper \%hash;
$VAR1 = {
'1' => 2,
'3' => 4,
'5' => undef
};
As you can see, undef has been inserted in the empty spot. Now, you can check for undefined values and pretend that any existing undefined values constitutes an odd number of elements in the hash. However, should an undefined value be a valid value in your hash, you're in trouble.
perl -lwe '
sub isodd { my $count = #_ = grep defined, #_; return ($count % 2) };
%a=(a=>1,2);
print isodd(%a);'
Odd number of elements in hash assignment at -e line 1.
1
In this one-liner, the function isodd counts the defined arguments and returns whether the amount of arguments is odd or not. But as you can see, it still gives the warning.
You can use the __WARN__ signal to "trap" for when a hash assignment is incorrect.
use strict ;
use warnings ;
my $odd_hash_length = 0 ;
{
local $SIG{__WARN__} = sub {
my $msg = shift ;
if ($msg =~ m{Odd number of elements in hash assignment at}) {
$odd_hash_length = 1 ;
}
} ;
my %hash = (1, 2, 3, 4, 5) ;
}
# Now do what you want based on $odd_hash_length
if ($odd_hash_length) {
die "the hash had an odd hash length assignment...aborting\n" ;
} else {
print "the hash was initialized correctly\n";
}
See also How to capture and save warnings in Perl.

Perl hash value without key name

Is it possible in Perl to access a value of a hash, if it has just one key, without using key value?
Let's say, %h has just 'key_name' => 'value'.
Can I access the 'value' only via $h->{key_name}?
Or, is possible to access this 'value' without key name?
The values builtin function for hashes will return a list of all the hash values. You can use this to get or set any values with aliasing list constructs such as foreach, map, and grep:
for my $value (values %hash) {
say $value; # prints the value
$value++; # adds one to the value
}
Or you can store the values in an array:
my #vals = values %hash;
The order of the returned values is effectively random, but it will be the same order as the corresponding keys function.
Hashes themselves are lists, so you can access any odd element of the hash in list context to get at the value, but this method is less efficient since the whole hash needs to be taken apart to form the list, not just the values.
The techniques above work with hashes of any size. If you only have one key / value pair:
my %hash = qw(foo bar);
Then they reduce to:
{my ($x) = values %hash; say $x} # bar
{my (undef, $x) = %hash; say $x} # bar
{my $x = (values %hash)[0]; say $x} # bar
{my $x = (%hash)[1]; say $x} # bar
There are many ways to do this. For example:
my %h=("key_name"=>"value"); print values(%h)
or
my %h=("key_name"=>"value"); print( (%h)[1])
But in my opinion that doesn't look pretty...
You've got two options here - you can either optimize for space, or optimize for time. If you need to get the key from that value and you don't care about how long it takes, you can iterate over each entry in the associative array:
while(($key, $value) = each(%h))
{
if($value eq 'value')
{
return $key;
}
}
But if you don't mind having two copies, the most time-efficient solution is to hold a backwards and forwards associative array -- that is: %h_by_name and %h_by_value.
In this case, if you have multiple keys with the same value, your %h_by_value should contain an array. That is if:
%h_by_name = (
"a" => "1",
"b" => "1",
"c" => "1",
"d" => 2"
);
Then you would want to construct your %h_by_value such that it was:
%h_by_value = (
"1" => [ "a", "b", "c" ],
"2" => [ "d" ]
);

Perl hash when both the keys and the values are array references

I have a problem where pairs of numbers map to other pairs of numbers. For instance, (1,2)->(12,97). Some pairs may map to multiple other pairs, so what I really need is the ability to map a pair into a list of lists, like (1,2)->((12,97),(4,1)). At the end of the day I want to process each of the values (i.e., each list of lists) separately.
In Python, I could do this by simply saying:
key = ( x, y )
val = [ a, b ]
if (x,y) not in my_dict:
my_dict[ (x,y) ] = []
my_dict[ (x,y) ].append( [a,b] )
However, in Perl, I have to use refs for the keys and values. So I can certainly say:
$keyref = [ x1, y1 ]
$valref = [ a, b ]
%my_hash = { $keyref => $valref }
But what happens when another pair (x2,y2) comes along? Even if x2==x1 and y2==y1, $keyref=[x2,y2] will differ from the previous keyref generated, so I do not see a way to do the lookup. Of course, I could compare (x2,y2) with each dereferenced hash key, but after all, God gave us hash tables precisely to avoid the need to do so.
Is there a Perl solution?
Thanks,
-W.
In Perl, all hash keys are strings, or are "stringified" before lookup. Using an array reference as a key is usually the wrong approach.
What about using a "two-dimensional" hash?
$hash{$x1}{$y1} = [ $a, $b ];
# or
%hash = ( $x1 => { $y1 => [ $a, $b ] } );
($x2,$y2)=($x1,$y1);
print #{$hash{$x2}{$y2}}; # will print $a and $b
Like most things in Perl, TMTOWTDI.
Option 1: Use multidimensional array emulation
$hash{$x,$y} = [$a, $b];
See also the documentation for the built-in variable $;.
Option 2: Use the Hash::MultiKey module
tie %hash, 'Hash::MultiKey';
$hash{[$x, $y]} = [$a, $b];
Option 3: Use a HoH (hash of hashes) instead
$hash{$x}{$y} = [$a, $b];
I ended up using Socket Puppet's solution (in the form of Michael Carmen's Option 3). FYI, here is a little Perl script that carries out all the operations I need in my app.
Printed lines 2:,3: and 4:,5: just use different syntax to do the same thing, and lines 0: and 1: were just intended as sanity checks along the way.
What this this adds to the suggested solution is the use of an array of arrays as the value that goes along with a key.
#k1 = ( 12, 13 );
$aref = [ 11, 22 ];
$bref = [ 33, 44 ];
%h = {};
if( not exists $h{$k1[0]}{$k1[1]} ) {
print "initializing\n";
$h{$k1[0]}{$k1[1]} = [];
}
push #{$h{$k1[0]}{$k1[1]}}, $aref;
push #{$h{$k1[0]}{$k1[1]}}, $bref;
print "0: ", join ':', #{$h{$k1[0]}{$k1[1]}}, "\n";
print "1: ", join ':', ${$h{$k1[0]}{$k1[1]}}[0], "\n";
print "2: ", join ':', #{${$h{$k1[0]}{$k1[1]}}[0]}, "\n";
print "3: ", join ':', #{${$h{$k1[0]}{$k1[1]}}[1]}, "\n";
print "4: ", join ':', #{$h{$k1[0]}{$k1[1]}->[0]}, "\n";
print "5: ", join ':', #{$h{$k1[0]}{$k1[1]}->[1]}, "\n";
P.S. I would have added this as a comment but it was too long, and I thought it made sense to include a worked example.

What perl code samples can lead to undefined behaviour?

These are the ones I'm aware of:
The behaviour of a "my" statement modified with a statement modifier conditional or loop construct (e.g. "my $x if ...").
Modifying a variable twice in the same statement, like $i = $i++;
sort() in scalar context
truncate(), when LENGTH is greater than the length of the file
Using 32-bit integers, "1 << 32" is undefined. Shifting by a negative number of bits is also undefined.
Non-scalar assignment to "state" variables, e.g. state #a = (1..3).
One that is easy to trip over is prematurely breaking out of a loop while iterating through a hash with each.
#!/usr/bin/perl
use strict;
use warnings;
my %name_to_num = ( one => 1, two => 2, three => 3 );
find_name(2); # works the first time
find_name(2); # but fails this time
exit;
sub find_name {
my($target) = #_;
while( my($name, $num) = each %name_to_num ) {
if($num == $target) {
print "The number $target is called '$name'\n";
return;
}
}
print "Unable to find a name for $target\n";
}
Output:
The number 2 is called 'two'
Unable to find a name for 2
This is obviously a silly example, but the point still stands - when iterating through a hash with each you should either never last or return out of the loop; or you should reset the iterator (with keys %hash) before each search.
These are just variations on the theme of modifying a structure that is being iterated over:
map, grep and sort where the code reference modifies the list of items to sort.
Another issue with sort arises where the code reference is not idempotent (in the comp sci sense)--sort_func($a, $b) must always return the same value for any given $a and $b.