How to access a customize hash structure in Perl? - perl

I have a custom perl hash data structure . The sturcture is like bellow:
%myhash = (
1 => {
'scf1' => [
1,3,0,4,6,7,8,
],
'sef2' => [
10,15,20,30,
]
},
2 => {
'scf1' => [
10,3,0,41,6,47,81,
],
'scf3' => [
1,66,0,123,4,1,2435,33445,1
]
},
);
How I can access this kind of perl structure.

I'm afraid your code ... is showing signs that you are misunderstanding what hashes do, and how they work. Specifically, when you're referencing #{$myhash} - this is NOT the same as the %myhash that you undef.
Likewise - what's going on with #features? It looks like you're trying to build an array of arrays, but doing so by iterating through fetchrow_array and then pushing. Multidimensional arrays are sometimes the right tool for the job, but it is unclear why it would be suitable for what you're doing. (After all, you don't use it for anything else in this piece of code).
You've also got $line[2] - which is also not doing what you might think - it does NOT refer to $line, it's the second element of a list called #line - which doesn't exist.
You are also trying to process is list of database entries, and set it '-1' if it's undef.
We need some more detail about what data you're getting out of your database - $sth -> fetchrow_array() could be anything. However, I'd strongly suggest that what you want to do is name each of the fields as you go. I'd suggest you DON'T want to be using $line there, because it's ... well, wrong. You're iterating columns in the row you've just fetched.
Which field in your fetched array are the keys to your hash? It looks like you're trying to key on 'field 5' 'field 7' and trying to insert values of 'field 1' and 'field 2'. Is that correct?
Oh, and turn on use strict; use warnings whilst you're at it.

get the inner array:
my #array = #{$hash{1}->{'scf1'}};
# is same as
# my $array_ref = $hash{1}->{'scf1'};
# my #array = #{$array_ref};
# then you can
my $some_thing = $array[0];
or get one element:
$hash{1}->{'scf1'}->[0];

From your Data::Dumper dump, I see that you have a hash called %myhash. Each element in that hash contains a reference to another hash. And, each element in that inner hash contains a reference to an array.
Let's take your Data::Dumper, and restate it like this:
$myhash{1}->{sff1} = [1, 3, 0, 4, 6, 7, 8];
$myhash{1}->{sef2} = [10, 15, 20, 30];
$myhash{2}->{scf1} = [10, 3, 0, 41, 6, 47, 81];
$myhash{2}->{scf3} = [1, 66, 0, 123, 4, 2435, 33445, 1];
Same thing. It's just a bit more compact.
To print this out, we'll need to loop through each of these layers of references:
#
# First loop: The outer hash which is a plain normal hash
#
for my $outer_key ( sort keys %myhash ) {
#
# Each element in that hash points to another hash reference. Dereference
#
my %inner_hash = %{ $myhash{$outer_key} };
for my $inner_key ( sort keys %inner_hash ) {
#
# Finally, this is our array reference in the inner hash. Let's dereference and print
#
print "\$myhash{$outer_key}->{$inner_key}: ";
my #array = #{ $myhash{$outer_key}->{$inner_key} };
for my $value ( #array ) {
print "$value";
}
print "\n";
}
}

Related

Perl: Access hash of dynamic depth

I am struggling with accessing/ modifying hashes of unknown (i.e. dynamic) depth.
Suppose I am reading in a table of measurements (Length, Width, Height) from a file, then calculating Area and Volume to create a hash like the following:
# #Length Width Height Results
my %results = (
'2' => {
'3' => {
'7' => {
'Area' => 6,
'Volume' => 42,
},
},
},
'6' => {
'4' => {
'2' => {
'Area' => 24,
'Volume' => 48,
},
},
},
);
I understand how to access a single item in the hash, e.g. $results{2}{3}{7}{'Area'} would give me 6, or I could check if that combination of measurements has been found in the input file with exists $results{2}{3}{7}{'Area'}. However that notation with the series of {} braces assumes I know when writing the code that there will be 4 layers of keys.
What if there are more or less and I only discover that at runtime? E.g. if there were only Length and Width in the file, how would you make code that would then access the hash like $results{2}{3}{'Area'}?
I.e. given a hash and dynamic-length list of nested keys that may or may not have a resultant entry in that hash, how do you access the hash for basic things like checking if that key combo has a value or modifying the value?
I almost want a notation like:
my #hashkeys = (2,3,7);
if exists ( $hash{join("->",#hashkeys)} ){
print "Found it!\n";
}
I know you can access sub-hashes of a hash and get their references so in this last example I could iterate through #hashkeys, checking for each one if the current hash has a sub-hash at that key and if so, saving a reference to that sub-hash for the next iteration. However, that feels complex and I suspect there is already a way to do this much easier.
Hopefully this is enough to understand my question but I can try to work up a MWE if not.
Thanks.
So here's a recursive function which does more or less what you want:
sub fetch {
my $ref = shift;
my $key = shift;
my #remaining_path = #_;
return undef unless ref $ref;
return undef unless defined $ref->{$key};
return $ref->{$key} unless scalar #remaining_path;
return fetch($ref->{$key}, #remaining_path);
}
fetch(\%results, 2, 3, 7, 'Volume'); # 42
fetch(\%results, 2, 3); # hashref
fetch(\%results, 2, 3, 7, 'Area', 8); # undef
fetch(\%results, 2, 3, 8, 'Area'); # undef
But please check the comment about bad data structure which is already given by someone else, because it's very true. And if you still think that this is what you need, at least rewrite it using a for-loop, as perl does not optimize tail recursion.
Take a look at $; in "man perlvar".
http://perldoc.perl.org/perlvar.html#%24%3b
You may use the idea to convert variable length array into single key.
my %foo;
my (#KEYS)=(2,3,7);
$foo{ join( $; , #KEYS ) }{Area}=6;
$foo{ join( $; , #KEYS ) }{Volume}=42;

How to alter the hash values with new values in perl?

How can i reset the hash values in perl
use warnings;
use strict;
my %hash = qw(one 1 two 2 three 3 four 4);
my #key = keys(%hash);
my #avz = (9..12);
my %vzm;
print "Original hash and keys : ",%hash,"\n";
for(my $i = 0; $i<=scalar #avz; $i++){
my #new = "$key[$i] $avz[$i] ";
push(%vzm , #new);
}
print "modified hash and keys",%vzm,"\n";
I tried to alter the keys of original hash with another keys. How can i do it
This program give the error is:
Original hash and keys : three3one1two2four4
Not an ARRAY reference at key.pl line 10.
I expect the output is
Original hash and keys : three3one1two2four4
modified hash and keys : three11one9two10four12
How can i do it
Ok, first off - you're doing something nasty in your code:
You're trying to take an ordered data structure - an array - and push it into a keyed data structure, which has no particular ordering defined.
This isn't going to work very well - it technically works, because internally perl treats arrays and hashes similarly.
But for example your first assignment - what you're actually getting is:
my %hash = (
one => 1,
two => 2,
three => 3,
four => 4
);
You can access the keys (in no particular order) via keys(). And the values via values(). But to try and treat it like an array is undefined behaviour.
To add elements to your array:
$hash{'nine'} = 9;
To delete elements from your array:
delete ( $hash{'one'} );
You can iterate on keys or values - and combined with sort even do them in some sort of order. (Just bear in mind for sorting alphanumeric numbers you'll have a custom sort job).
foreach my $key ( sort keys %hash ) {
print "$key => $hash{$key}\n";
}
(Note - this is sorting by alphanumeric string, so gives:
four => 4
one => 1
three => 3
two => 2
If you want to sort by value:
foreach my $key ( sort { $hash{$a} <=> $hash{$b} } keys %hash ) {
print "$key => $hash{$key}\n";
}
And so you'll get:
one => 1
two => 2
three => 3
four => 4
So the real question remains - what are you actually trying to accomplish? The point of a hash is to give you an unordered mini-database of key-value pairs. Treating one like an array doesn't make an awful lot of sense. Either you're iterating hash elements in arbitrary order, or you're applying a specific sort to it - but one where you're relying on getting elements in a particular order is a bad plan - it may work, but it's not guaranteed to work, and that makes for bad code.
You have to keep the order of the keys in some array, or take it from original list
my #tmp = qw(one 1 two 2 three 3 four 4);
my %hash = #tmp;
# 'one', 'two', ..
my #key = #tmp[ grep !($_%2), 0 .. $#tmp ];
# ..
for my $i (0 .. $#avz) {
$vzm{ $key[$i] } = $avz[$i];
}
or using hash slice as more perlish approach,
#vzm{ #key } = #avz;
You can't do what you want (replace the values for keys in the hash in the order they originally were added) without keeping track of that order separately, since the hash doesn't have any particular order. In other words, this:
my #key = keys(%hash);
needs to be this:
my #key = ( 'one', 'two', 'three', 'four' );
Once you have that, you can just assign the values all at once with a hash slice:
my %vzm;
#vzm{#key} = #avz;
To create a hash element, you use assignment to $var{$key}.
for (my $i = 0; $i < scalar #avz; $i++) {
$vzm{$key[$i]} = $avz[$i];
}
Note also that the loop condition should be <, not <=. List/array indexes end at scalar #avz - 1.

How to manipulate a hash-ref with Perl?

Take a look at this code. After hours of trial and error. I finally got a solution. But have no idea why it works, and to be quite honest, Perl is throwing me for a loop here.
use Data::Diff 'Diff';
use Data::Dumper;
my $out = Diff(\#comparr,\#grabarr);
my #uniq_a;
#temp = ();
my $x = #$out{uniq_a};
foreach my $y (#$x) {
#temp = ();
foreach my $z (#$y) {
push(#temp, $z);
}
push(#uniq_a, [$temp[0], $temp[1], $temp[2], $temp[3]]);
}
Why is it that the only way I can access the elements of the $out array is to pass a hash key into a scalar which has been cast as an array using a for loop? my $x = #$out{uniq_a}; I'm totally confused. I'd really appreciate anyone who can explain what's going on here so I'll know for the future. Thanks in advance.
$out is a hash reference, and you use the dereferencing operator ->{...} to access members of the hash that it refers to, like
$out->{uniq_a}
What you have stumbled on is Perl's hash slice notation, where you use the # sigil in front of the name of a hash to conveniently extract a list of values from that hash. For example:
%foo = ( a => 123, b => 456, c => 789 );
$foo = { a => 123, b => 456, c => 789 };
print #foo{"b","c"}; # 456,789
print #$foo{"c","a"}; # 789,123
Using hash slice notation with a single element inside the braces, as you do, is not the typical usage and gives you the results you want by accident.
The Diff function returns a hash reference. You are accessing the element of this hash that has key uniq_a by extracting a one-element slice of the hash, instead of the correct $out->{uniq_a}. Your code should look like this
my $out = Diff(\#comparr, \#grabarr);
my #uniq_a;
my $uniq_a = $out->{uniq_a};
for my $list (#$uniq_a) {
my #temp = #$list;
push #uniq_a, [ #temp[0..3] ];
}
In the documentation for Data::Diff it states:
The value returned is always a hash reference and the hash will have
one or more of the following hash keys: type, same, diff, diff_a,
diff_b, uniq_a and uniq_b
So $out is a reference and you have to access the values through the mentioned keys.

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.