How to get the key associated with a hash reference's key in a hash of hashes? - perl

In an attempt to help me learn Perl, I built the following data structure, where the inner hash (/DriveA/archive, etc.) is a hash reference:
#The contents of the %properties hash of hashes
#The inner hash is a hash reference to a hash named %attributes
$VAR1 = {
'/DriveA' => {
'/DriveA/archive/' => {
'MaxSize' => '20GB',
'Size' => '19GB',
'Free' => '5'
},
'/DriveA/current/' => {
'MaxSize' => '20GB',
'Size' => '12GB',
'Free' => '40'
}
},
'/DriveB' => {
'/DriveB/archive/' => {
'MaxSize' => '8GB',
'Size' => '6GB',
'Free' => '25'
},
'/DriveB/current/' => {
'MaxSize' => '80GB',
'Size' => '20GB',
'Free' => '75'
}
},
'/DriveC' => {
'/DriveC/' => {
'MaxSize' => '20GB',
'Size' => '10GB',
'Free' => '50'
}
}
}
I created an array to hold the keys for %attributes (aka the values/hash reference in %properties) using:
#list = sort keys %attributes;
I'm trying to iterate over the elements in #list and find the associated key for the outer hash in %properties. So, for example, if /DriveA/archive/ is the next item in the array, I want to find the hash key associated with that value, /DriveA, from %properties, assuming dereference of the inner hash.
I created a reverse hash, which outputs the following...
$VAR1 = {
'HASH(0x2002f244)' => '/DriveB',
'HASH(0x2002f388)' => '/DriveC',
'HASH(0x2002f1e4)' => '/DriveA'
}
...using this code...
foreach my $item (#list) {
my %rhash = reverse %properties; # Reverse the hash of hashes so value is key
print Dumper(\%rhash);
}
Question 1:
Given the above, how would I dereference the hash so I can find $item in the hash reference so I can determine the associated value (not the hash reference value).
If $item = '/DriveA/archive/', I want to capture '/DriveA' in a variable from %properties so it can be returned from a subroutine.
I know the inner hash needs to be dereferenced, I'm just not sure how to do it. I've read through perlref, perldsc, and perllol, but I haven't been able to find the answer.
Thanks.

The easiest thing to do is to just generate the reverse keys directly by traversing the data structure:
my %reverse_keys;
foreach my $outer_key (keys %properties) {
my $inner_hashref = $properties->{$outer_key};
my %reverse_map = map { ($_ => $outer_key) } keys %$inner_hashref;
%reverse_keys = (%reverse_keys, %reverse_map);
}
Ideally, you can even generate %reverse_keys directly at the same time you're stashing data into %properties, so you don't need that extra traversal code above.
To address your actual technical question:
You are starting with a hash reference (basically, a pointer in C vernacular).
Then you assign that hash reference as a key into a hash.
Assigning something as a hash key puts it into a string context (a special case of scalar context).
When you stringify a hash reference in Perl, it gets turned into a string representation of that reference - that's your HASH(0x2002f244) string you see when you Data::Dumper your reverse hash.
What you're asking is, on a technical level, is "how do I convert the string representation of a hash reference back into the hash reference itself?"
This is covered in Perl FAQ 4. As far as I'm aware, you can NOT easily convert the string representation of a hash reference back into the hash reference itself.
If you absolutely must (which I strongly recommend against - instead, use the solution at the top of the answer) - you can do so using Devel::Pointer CPAN module.
A solution using that module is shown in this PerlMonks thread.
An additional solution may be to use Tie::RefHash module to use actual hash references instead of their string representations as keys. This is documented in Chapter 8.5.1. "References Don't Work as Hash Keys" of O'Reilly's "Programming Perl, 3rd edition". I would recommend against that bit of madness as well.

Related

How to get the hash keys and values of a hash in perl mojo

I try to figure out how to select explicit key and values of a hash.
my $hash = query('select id, name, required from table')->hashes
My output when I dump is:
var1 = bless ([
{
'name' => value,
'id' => value,
'required' => value
}.....
])
What I want is to get the follow output:
var1 = bless ([
{
'required' => value
}...
])
After that I want to compare if the index == index of another array.
You removed the class name from bless, but I guess it's Mojo::Collection. Use it's map method to iterate over the elements:
my $required = $hash->map(sub { required => $_->{required} });
# Untested.
Also, using the name $hash for something that's a collection of hashes is confusing.
I don't understand your last sentence about the index. If you want to extract the $index-th element, you can use
my $hash = $required->to_array->[$index];
or to get directly the value
my $value = $required->to_array->[$index]{required};
Once you get a hash reference, from whatever means, you access the keys like this:
$hash->{required}; # etc.
See perlref for more information. Also read perldsc and perlreftut.

How do I decipher an array of hashes?

I totally got this question wrong. Am using the method from TMDB:
my #results = $search->find(id => 'tt0114694', source => 'imdb_id');
I thought the output was in JSON format, so that's what confused me, which kept me running in circles because I was looking at it all wrong.
Didn't realize the data below, from Dumper, was the actual hashes the I had to go through.
This is where I am running into a wall, So the data below is a hash with five keys. The fifth key, the I want, contains another array. It is that array I cannot read into. I try dereferencing that into a hash, and that is where I fail.
The code I am trying is:
foreach my $narray (#results){
print $narray->{"movie_results"};
my #newarray = $narray->{"movie_results"};
foreach my $otherarray (#newarray){
my %innerhash = $otherarray;
print %innerhash;
print "\n";
}
}
It will print out an array, but I am unable to read the hash in that array.
p.s. I had to format this output as code, or else it came out with no line breaks.
$VAR1 = {
'tv_season_results' => [],
'tv_results' => [],
'person_results' => [],
'tv_episode_results' => [],
'movie_results' => [
{
'adult' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
'vote_average' => '6.8',
'original_title' => 'Tommy Boy',
'vote_count' => 635,
'id' => 11381,
'release_date' => '1995-03-31',
'overview' => 'Party animal Tommy Callahan is a few cans short of a six-pack. But when the family business starts tanking, it\'s up to Tommy and number-cruncher Richard Hayden to save the day.',
'genre_ids' => [
35
],
'title' => 'Tommy Boy',
'video' => $VAR1->{'movie_results'}[0]{'adult'},
'poster_path' => '/g32WbO9nbY5ydpux5hIoiJkLEQi.jpg',
'original_language' => 'en',
'backdrop_path' => '/bZ4diYf7oyDVaRYeWG42Oify2mB.jpg',
'popularity' => '13.945'
}
]
};
You mention that you thought you'd get JSON output, but got something else. The module made a web request for you, received the JSON response, and translated that to a Perl data structure. That Perl version of the JSON is what you see in the dump.
A JSON object turns into a Perl hash, so that's what you see in the top level of the data structure. That's the single thing find returns (more on that in a moment):
Here's what you have, removing the outer foreach loop:
my #newarray = $narray->{"movie_results"};
foreach my $otherarray (#newarray){
my %innerhash = $otherarray;
print %innerhash;
print "\n";
}
The value in $narray->{"movie_results"} is an array reference. All references are scalars, and those scalars point to some data structure. When you assign that scalar to an array, you just end up with a one element array that's the same reference. Instead, you can
my $movie_results = $narray->{"movie_results"};
You then dereference that reference to treat it as an array:
foreach my $result ( #$movie_results ){ ... }
Or, the v5.24 postfix dereferencing way that I find slightly more pleasing since it reads better, especially when you skip the intermediate variable:
foreach my $result ( $movie_results->#* ){ ... }
foreach my $result ( $narray->{"movie_results"}->#* ){ ... }
That thing in $result is another hash reference.
References and data structures are about half of the content of Intermediate Perl, but there is also the Perl data structures cookbook (perldsc).
Improving your question a bit
You can help us a lot by showing us a complete, working demonstration of your problem. Here's what I cobbled together:
use v5.10;
use TMDB;
use Data::Dumper;
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
my #results = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
say Dumper( \#results );
There was a question about the results of find. The documentation example shows it returning a list (well, the result being assigned to a named array, which implies that), but there's not actual documentation for find. It returns the decoded JSON from the response. Assigning it to a scalar (which will be a reference) works just fine too:
my $result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
say Dumper( $results );
The return value comes from TMDB::Sesssion::talk(), which is just this (or the empty list):
return $self->json->decode(
Encode::decode( 'utf-8-strict', $response->{content} ) );
That's not a big deal. That just means you don't need the outer foreach. That's not on you because the example in the docs tells you to do exactly what you did.
Now a better program
Putting all that together, here's a simple program pared down to just what you need:
use v5.10;
use TMDB;
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
my $result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
foreach my $item ( $result->{movie_results}->#* ) {
say "Title: $item->{title}";
}
Ref aliasing
There's an experimental feature called ref aliasing that lets you assign a reference to a reference of a named variable. It's an alias, so you can access and change the same data, just with a named variable. Something this is handy when you don't like
use v5.10;
use TMDB;
use experimental qw(refaliasing);
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
# response is a hash ref, so ref alias to a named hash
\my %result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
# the part you want is an array ref, so alias that
\my #movie_results = $result{movie_results};
# each item in the array is a hash ref, so alias those too
foreach \my %item ( #movie_results ) {
say "Title: $item{title}";
}
When dealing with reference, use the same syntax as if you weren't, but replace the name of the variable with a block that returns the reference.
%NAME -> %{ $ref } Or just %$ref
$NAME{...} -> ${ $ref }{...} Although $ref->{...} easier to read.
#NAME -> #{ $ref } Or just #$ref
$NAME[...] -> ${ $ref }[...] Although $ref->[...] easier to read.
Let's give $VAR a better name,
my $response = $VAR1;
This means you want
my $results = $response->{movie_results};
for my $result (#$results) {
for my $key (keys(%$result)) {
say "$key: $result->{$key}";
}
}
See
perlreftut
Perl Dereferencing Syntax
%newhash{$newkey} should be $newhash{$newkey}.

Perl: Array of hashes - Get the element with id in hash

I have a Perl data structure, loaded from a json, with Data::Dumper looking like this:
$VAR1 = {
'Stat' => [
{
'statCfgFile' => 'statcfg_0001.json',
'statid' => 1,
'status' => 'running',
'something' => 'other'
},
{
'statCfgFile' => 'statcfg_0002.json',
'statid' => 2,
'status' => 'running'
'something' => 'other'
}
]
}
From another dataset, I get a statid to read the hash, but the given id does not match the array key, therefore I need to go into deep, reading the statid property.
Of course, I could loop through the array, but with a large array list, that might hit performance.
Is there a way to directly access the array element by the statid stored in the hash?
As the data model is in my hands and currently in development:
Would it be better not to use an array, but also a hash with the statid as the naming element?
The short answer is no, you can't pull a value out of an array based on a criteria without iterating over the array. But there are ways to write it without using a loop.
Say the data structure is $data and you want to pull hashes where statid equals $id_to_find.
my #matching_hashes = grep {$_->{statid} = $id_to_find}} #{$data->{Stat}};
If only the first hash matching the ID is relevant, you could use the core module List::Util function first, which does the same thing as grep but only returns the first match instead of a list. It would be faster than grep because it stops iterating over the array as soon as it finds one match.
use List::Util 'first';
my $matching hash = first {$_->{statid} = $id_to_find}} #{$data->{Stat}};
As you indicated, using hash lookups is much faster than list operations. You could create a hash index of array adresses. Note this still requires you iterate over the array once.
# create the index;
my %index;
my #array = #{$data->{Stat}};
for my $address (0..$#array) { # "$#array" is the last element of #array
my $hash = $array[$address];
my $id = $hash->{statid};
$index{$id} = $address; # now you can use an ID to get the array address
}
# use the index
my $id_to_find = 42;
my $wanted_array_address = $index{$id_to_find};
my $matching_hash = $data->{Stat}->[$wanted_array_address];
If the statid field is unique then I suggest you use something more like this
{
1 => 'statcfg_0001.json',
2 => 'statcfg_0002.json',
}
The hash includes more data than shown. I extend the example
Then you need a hash instead of just a string value for each statid
{
1 => {
statCfgFile => 'statcfg_0001.json',
statid => 1,
status => 'running',
something => 'other',
},
2 => {
statCfgFile => 'statcfg_0002.json',
statid => 2,
status => 'running',
something => 'other',
},
}

How to merge two hashes returned from sub, in Perl

I am trying to merge two hashes. But they are return values from functions. How can I dereference the return values inline? I don't want to use extra variables such as my $pos = makePos();
use v5.8.8;
use strict;
use warnings;
sub _makePos
{
my $desc= {
pos50 => {unit => 'm', desc => 'position error' },
pos95 => {unit => 'm', desc => '95% position error' }
};
return $desc;
}
sub _makeVel
{
my $desc= {
vel50 => {unit => 'm/s', desc => 'velocity error' },
vel95 => {unit => 'm/s', desc => '95% velocity error' }
};
return $desc;
}
my $descs = {_makePos(), _makeVel()};
use Data::Dumper;
print Dumper($descs);
this prints only the hash returned from _makeVel. how does it work?
$VAR1 = {
'HASH(0x21ea4a0)' => {
'vel50' => {
'desc' => 'velocity error',
'unit' => 'm/s'
},
'vel95' => {
'unit' => 'm/s',
'desc' => '95% velocity error'
}
}
};
changing this line as
my $descs = {%{_makePos()}, %{_makeVel()}};
worked!
Actually, your original solution did print both of the hashes, but the first one was "stringified" as it was being used as the key of your hash. It's there as HASH(0x21ea4a0).
I see you have a solution, but it might be worth explaining what was going wrong and why your solution fixed it.
Your two subroutines don't return hashes but, rather, hash references. A hash reference is a scalar value that is, effectively, a pointer to a hash.
A hash is created from a list of values. Your code that creates your news hash (actually, once again, a hash reference) is this:
my $descs = {_makePos(), _makeVel()};
This is two scalar values. The first is used as a key in the new hash and the second is used as the associated value - hence the results you get from Data::Dumper.
What you actually want to do is to "dereference" your hashes and get back to the actual hashes. You can dereference a hash using the syntax %{ ... }, where the ... is any expression returning a hash reference. And that's what you've done in your solution. You dereference the hash references, which gives you a list of key/value pairs. The pairs from the two dereferenced hashes are then joined together in a single list which is used to create your new, combined, hash.
I should point out that there's a danger in this approach. If your two subroutines can ever return references to hashes that contain the same key, then only one version of that repeated key will appear in the combined hash.

What are anonymous hashes in perl?

$hash = { 'Man' => 'Bill',
'Woman' => 'Mary,
'Dog' => 'Ben'
};
What exactly do Perl's “anonymous hashes” do?
It is a reference to a hash that can be stored in a scalar variable. It is exactly like a regular hash, except that the curly brackets {...} creates a reference to a hash.
Note the usage of different parentheses in these examples:
%hash = ( foo => "bar" ); # regular hash
$hash = { foo => "bar" }; # reference to anonymous (unnamed) hash
$href = \%hash; # reference to named hash %hash
This is useful to be able to do, if you for example want to pass a hash as an argument to a subroutine:
foo(\%hash, $arg1, $arg2);
sub foo {
my ($hash, #args) = #_;
...
}
And it is a way to create a multilevel hash:
my %hash = ( foo => { bar => "baz" } ); # $hash{foo}{bar} is now "baz"
You use an anonymous hash when you need reference to a hash and a named hash is inconvenient or unnecessary. For instance, if you wanted to pass a hash to a subroutine, you could write
my %hash = (a => 1, b => 2);
mysub(\%hash);
but if there is no need to access the hash through its name %hash you could equivalently write
mysub( {a => 1, b => 2} );
This comes in handy wherever you need a reference to a hash, and particularly when you are building nested data structures. Instead of
my %person1 = ( age => 34, position => 'captain' );
my %person2 = ( age => 28, position => 'boatswain' );
my %person3 = ( age => 18, position => 'cabin boy' );
my %crew = (
bill => \%person1,
ben => \%person2,
weed => \%person3,
);
you can write just
my %crew = (
bill => { age => 34, position => 'captain' },
ben => { age => 28, position => 'boatswain' },
weed => { age => 18, position => 'cabin boy' },
);
and to add a member,
$crew{jess} = { age => 4, position => "ship's cat" };
is a lot neater than
my %newperson = ( age => 4, position => "ship's cat" );
$crew{jess} = \%newperson;
and of course, even if a hash is created with a name, if its reference is passed elsewhere then there may be no way of using that original name, so it must be treated as anonymous. For instance in
my $crew_member = $crew{bill}
$crew_member is now effectively a reference to an anonymous hash, regardless of how the data was originally constructed. Even if the data is (in some scope) still accessible as %person1 there is no general way of knowing that, and the data can be accessed only by its reference.
It's quite simple. They allow you to write
push #hashes, { ... };
f(config => { ... });
instead of
my %hash = ( ... );
push #hashes, \%hash;
my %config = ( ... );
f(config => \%config);
(If you want to know the purpose of references, that's another story entirely.)
Anything "anonymous" is a data structure that used in a way where it does not get a name.
Your question has confused everyone else on this page, because your example shows you giving a name to the hash you created, thus it is no longer anonymous.
For example - if you have a subroutine and you want to return a hash, you could write this code:-
return {'hello'=>123};
since it has no name there - it is anonymous. Read on to unwind the extra confusion other people have added on this page by introducing references, which are not the same thing.
This is another anonymous hash (an empty one):
{}
This is an anonymous hash with something in it:
{'foo'=>123}
This is an anonymous (empty) array:
[]
This is an anonymous array with something in it:
['foo',123]
Most of the time when people use these things, they are really trying to magically put them inside of other data structures, without the bother of giving them a waste-of-time temporary name when they do this.
For example - you might want to have a hash in the middle of an array!
#array=(1,2,{foo=>3});
that array has 3 elements - the last element is a hash! ($array[2]->{foo} is 3)
perl -e '#array=(1,2,{foo=>1});use Data::Dumper;print Data::Dumper->Dump([\#array],["\#array"]);'
$#array = [
1,
2,
{
'foo' => 1
}
];
Sometimes you want to don't want to pass around an entire data structure, instead, you just want to use a pointer or reference to the data structure. In perl, you can do this by adding a "\" in front of a variable;
%hashcopy=%myhash; # this duplicates the hash
$myhash{test}=2; # does not affect %hashcopy
$hashpointer=\%myhash; # this gives us a different way to access the same hash
$hashpointer->{test}=2;# changes %myhash
$$hashpointer{test}=2; # identical to above (notice double $$)
If you're crazy, you can even have references to anonymous hashes:
perl -e 'print [],\[],{},\{}'
ARRAY(0x10eed48)REF(0x110b7a8)HASH(0x10eee38)REF(0x110b808)
and sometimes perl is clever enough to know you really meant reference, even when you didn't specifically say so, like my first "return" example:
perl -e 'sub tst{ return {foo=>bar}; }; $var=&tst();use Data::Dumper;print Data::Dumper->Dump([\$var],["\$var"]);'
$var = \{
'foo' => 'bar'
};
or:-
perl -e 'sub tst{ return {foo=>bar}; }; $var=&tst(); print "$$var{foo}\n$var->{foo}\n"'
bar
bar