Perl Hash of Hashes Storing References To Within - Can't use string ("") as a HASH ref - perl

Alright I have a function which generates a hash tree that dumper prints out to look like this:
$VAR1 = {
'shaders' => {
'stock_gui.vert' => '',
'stock_font.vert' => '',
'stock_gui.frag' => '',
'stock_font.frag' => ''
},
'textures' => {},
'fonts' => {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
}
};
Now I am trying to dfs iterate for example the fonts sub tree:
push (#stack, \%{$myHash->{'fonts'}});
Then in a loop:
my $tmpHash = pop(#stack);
Then a dumper of $tmpHash shows:
$VAR1 = {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
};
The problem is trying access the children of the hash reference. I can count the keys and see the children. The dumper output looks okay. However trying to do something like:
foreach my $childKey ( keys $tmpHash ){
my $subChildrenCount = scalar keys(%{$tmpHash->{$childKey}});
}
Yields the error:
Can't use string ("") as a HASH ref while "strict refs" in use
I think this is because $tmpHash is a hash reference. I likely just need to dereference it somewhere. I've tried many things and all yields further issues. Any help appreciated.
If I try:
%{$tmpHash->{'small'}}
Then it works fine.
UPDATE:
If the string contains a '.' then this error occurs. Hard coding 'small' works. Hard coding 'stock_gui.vert' fails unless I escape the '.'. However the keys do not match if I escape the dot...

As you can see by running it yourself,
use strict;
use warnings;
my $tmpHash = {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
};
my $subChildrenCount = scalar keys(%{$tmpHash->{'small'}});
the code you say gives that error does not actually give that error. I suspect you are actually doing
my $subChildrenCount = scalar keys(%{$tmpHash->{'DroidSansMono.ttf'}});
Your hash format doesn't make much sense. It mixes field names and actual data as keys.

Related

Array of Hashrefs: How to access based on hashref "column" values

I have an array of hashrefs built from a database using fethrow_hashref(). The data structure is built like so:
while (my $ref = $sth->fetchrow_hashref()) {
push #lines, $ref;
}
I sort the data in the query by program name ascending, so all of the references in the array are still in alphabetical order. Then, I go through each hash and find the value that is numerically equal to a '1'. I then take the caolumn name, and store it to compare to the rest of the hashrefs with that program name to ensure they all have a '1' in the same column.
my $pgm = "";
my $met_lvl = "";
my #devs = ();
my %errors = ();
my $error = "";
foreach my $line_ref (#lines) {
if ($pgm ne $line_ref->{"PROGRAM"}) {
if (#devs && $error) {
# print " Different number metal layers for $pgm: #devs \n";
$error = "";
}
#devs = ();
$pgm = $line_ref->{"PROGRAM"};
($met_lvl) = grep { $line_ref->{$_} == 1 } keys(%$line_ref);
push #devs, $line_ref->{"DEVICE"};
} elsif ($pgm eq $line_ref->{"PROGRAM"}) {
push #devs, $line_ref->{"DEVICE"};
my ($met_chk ) = grep { $line_ref->{$_} == 1 } keys(%$line_ref);
if ($met_chk ne $met_lvl) {
$errors{$line_ref->{"PROGRAM"}} = $line_ref->{"PROGRAM"};
$error = "YUP";
}
}
}
I'd like to be able to access the hashrefs individually, based on matching column names from the database. How can I access the hashrefs with "TEST" values for "PROGRAM" keys? I used Data::Dumper to provide an example of a few of the hashrefs I'd like to access based on "PROGRAM" value:
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV1',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'TEST'
};
$VAR455 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV2',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'TEST'
};
$VAR456 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV3',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NON_STANDARD',
'PROGRAM' => 'EXP'
};
$VAR457 = {
'PLM' => undef,
'SLM' => undef,
'QLM' => undef,
'DEVICE' => 'DEV4',
'TLM' => '1',
'DLM' => undef,
'ROUTING' => 'NORMAL',
'PROGRAM' => 'FINAL'
};
I'd like to be able to access key values for the hashrefs which contain the same program name. I cannot even begin to figure out what type of operation to use for this. I assume map is the correct way to do it, but dereferencing the "PROGAM" value for each element (hashref) in the array is beyond the scope of my understanding. I hope I was able to define the problem well enough for you guys to be able to help.
Edit: The impetus for wanting to access hashrefs with the same 'PROGRAM" value is to be able to provide an output of selected values to print to a logfile. So, after I compare and find differences between those hashrefs with the same "PROGRAM" value, I want to access them all again, and print out the desired column values to the lofgile.
Looks like you need to exrtact subsets of your data (hashrefs) with the same PROGRAM name.
Can preprocess your data to build a hash with those names as keys, and arrayrefs (with suitable hashrefs) as values. Then process those groups one at a time.
use warnings;
use strict;
use feature 'say';
use Data::Dumper; # to print complex data below
... populate #lines with hashrefs as in the question or copy-paste a sample
# Build hash: ( TEST => [ hashrefs w/ TEST ], EXP => [ hashrefs w/ EXP ], ... )
my %prog_subset;
for my $hr (#lines) {
push #{ $prog_subset{$hr->{PROGRAM}} }, $hr;
# Or, using "postfix dereferencing" (stable from v5.24)
# push $prog_subset{$hr->{PROGRAM}}->#*, $hr;
}
foreach my $prog (keys %prog_subset) {
say "\nProcess hashrefs with PROGRAM being $prog";
foreach my $hr (#{ $prog_subset{$prog} }) {
say Dumper $hr;
}
}
(See postfix dereference)
Now %prog_subset contains keys TEST, EXP, FINAL (and whatever other PROGRAM names are in data), each having for value an arrayref of all hashrefs which have that PROGRAM name.
There are other ways, and there are libraries that can be leveraged, but this should do it.
OK! I found an example of this with the google machine. I replaced #lines = (); with $lines = [];. This allowed me to change the grep statement to (#found) = grep { $pgm eq $_->{PROGRAM} } #$lines;. Now the returned array is a list of the hashrefs that share the program name I'm looking for. Thanks for the help #zdim!

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}.

insert anonymous hash into anonymous hash for counting in a loop

I'm trying to count starts and stops of some services i keep track of in logs.
I'm not going to past here entire code, but my way of doing hash is this:
I'm passing those starts and stops into anonymous hash .
First I'm creating anonymous hash filled with keys and values (in my case $knot is a key an zeros are values). Next im replaqcing values with another hash.
My code looks like this:
foreach $knot (#knots){
chomp $knot;
$variable = $variable."$knot;0;";
$Services = {split(/;/,$variable)};
}
my $data =
{
Starts=>'0',
Stops=>'0',
};
foreach my $key (keys %$Services) {
$Services->{$key} = $data;
}
print Dumper $Services;
Printing shows:
$VAR1 = {
' knot1' => {
'Stops' => '0',
'Starts' => '0'
},
' knot2' => $VAR1->{' knot1'},
' knot3' => $VAR1->{' knot1'},
' knot4' => $VAR1->{' knot1'},
' knot5' => $VAR1->{' knot1'},
and so on. Is there a better way of doing this? My way if i'm correct is badly written because changing knot1 starts/stops changes every other knot values.
Counting is very simple in Perl, thanks to Autovivification. You can just create anonymous data structures on the fly, like so:
use Data::Dumper;
my %hash = ();
$hash{apple}{green}++;
$hash{apple}{red} ++;
$hash{pear}{yellow}++;
$hash{apple}{green}++;
$hash{apple}{red} ++;
$hash{apple}{green}++;
print Dumper(\%hash);
This will produce the desired structure for counting:
$VAR1 = {
'apple' => {
'green' => 3,
'red' => 2
},
'pear' => {
'yellow' => 1
}
};
This also works in loops using variables (here using a reference to a hash):
my $hash_ref = {};
for my $fruit (qw( apple pear apple peach apple pear )) {
$hash_ref->{$fruit}++;
}
print Dumper($hash_ref);
resulting in:
$VAR1 = {
'peach' => 1,
'pear' => 2,
'apple' => 3
};

in perl ,how to use variable value as hash element

I am new to Perl, and can't find the answer to the question in the Learning Perl book.
For example I have a array like:
my #loop=("op1_sel","op2_sel");
and two hash table as:
my %op1_sel=(
"bibuf","000",
"self","101"
);
my %op2_sel=(
"zero","1",
"temp","0"
);
Now I want to use variables in the loop to loop for the hash table for a particular key
for example:
foreach(#loop)
{
print ${$_}{"bibuf"} ;
}
But it seems not working, I know the ${$_} part is wrong, can anyone can tell me how
to fix this ?
Use nested hashes. Like this:
my %op;
# put a hash reference into hash, twice
$op{op1_sel} = \%op1_sel;
$op{op2_sel} = \%op2_sel;
# later ...
foreach (keys %op) {
print "bibuf of $_: $op{$_}->{bibuf}\n";
};
Or, long story short, just
my %op = (
op1_sel => {
foo => 1,
bar => 2,
# ...
},
op2_sel => {
# ...
},
};
The {} construct creates a reference to anonymous hash and is the standard way of handling nested data structures.
See also perldoc perldsc.
You can't refer to lexical (my) variables using the ${$foo} syntax. You could probably make it work if they were package variables, but this would not be the right way to go about it.
The right way to do it is using a nested data structure.
I can see two obvious ways of doing it. You could either make an array of op_sel containing the inner hashes directly, or create a hash of hashes, and then index into that.
So "array of hashes":
my #op_sels = (
{
bibuf => '000',
self => '101',
},
{
zero => '1',
temp => '0',
},
);
for my $op (#op_sels) {
print $$op{bibuf};
}
and "hash of hashes":
my %op_sels = (
1 => {
bibuf => '000',
self => '101',
},
2 => {
zero => '1',
temp => '0',
},
);
for my $op_key (sort keys %op_sels) {
print $op_sels{$op_key}{bibuf};
}
You can use eval for this.
foreach(#loop)
{
eval "\%var = \%$_";
print $var{"bibuf"} ;
}

Perl: do and eval result in different answers?

I have a file with the following statements in it:
{
%{do '/tmp/personcontact.pl'},
%{do '/tmp/address.pl'}
}
Now, the temp files are as follows:
Personcontact.pl :
{
'firstname' => {
'__type' => 'String'
},
'lastname' => {
'__type' => 'String'
}
}
Address.pl:
{
'address' => {
'street' => {
'__type' => 'String'
},
'unit' => {
'__type' => 'String',
},
'suburb' => {
'__type' => 'String'
},
'__type' => 'HASH'
}
}
Now, when I do :
open(SCHEMAFILE, "<", $schema) or return undef;
my $schemafile;
while(my $line = <SCHEMAFILE>) { $schemafile .= $line;}
my $tempref = eval $schemafile;
print Dumper $tempref;
The result is $VAR1 = '1/8'
And when I do :
print Dumper do "/tmp/schemawithinschema.pl";
The result is
$VAR1 = 'firstname';
$VAR2 = {
'__type' => 'String'
};
$VAR3 = 'address';
$VAR4 = {
'suburb' => {
'__type' => 'String'
},
'unit' => {
'__type' => 'String'
},
'street' => {
'__type' => 'String'
},
'__type' => 'ARRAY'
};
$VAR5 = 'lastname';
$VAR6 = {
'__type' => 'String'
};
What's wrong here? Thanks!
Alright, to keep this from perpetuating forever, here is a module-based solution for you:
Foo.pm:
package Foo;
use strict;
use warnings;
BEGIN {
require Exporter;
our #ISA = qw( Exporter );
our #EXPORT_OK = qw( get_person get_address get_all );
our $VERSION = '0.01';
}
my %person = (
firstname => {
__type => 'String',
},
lastname => {
__type => 'String',
},
);
my %address = (
address => {
street => {
__type => 'String',
},
unit => {
__type => 'String',
},
suburb => {
__type => 'String',
},
__type => 'HASH',
},
);
sub get_person
{
return \%person;
}
sub get_address
{
return \%address;
}
sub get_all
{
return( { %person, %address } );
}
1;
__END__
bar.pl:
#!/usr/bin/perl
use Data::Dumper;
use strict;
use warnings;
use lib '.';
use Foo qw( get_person get_address get_all );
my $junk = get_all();
print Dumper $junk;
But really, for the sake of your maintenance programmer (often yourself in 6 months), use JSON or YAML (or the faster YAML::XS), so that the data can be maintained as a simple-ish text file, instead of a series of nested data-disguised-as-code references.
To quote Perl Best Practices (not sure if it was Damian originally):
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
EDIT: For completeness, here is the equivalent solution using YAML (from CPAN):
data.yml:
---
firstname:
__type: String
lastname:
__type: String
address:
__type: HASH
street:
__type: String
suburb:
__type: String
unit:
__type: String
baz.pl:
#!/usr/bin/perl
use YAML qw( Load Dump LoadFile DumpFile );
use Data::Dumper;
use strict;
use warnings;
my $data = LoadFile( 'data.yml' );
print Dumper $data;
One small pointer. That '1/8' is what you get when you evaluate a hash in a scalar context. The 8 is the number of buckets assigned to the hash and the 1 is the number of buckets that are in use.
It's generally useless, other than as a flag that you're doing something wrong.
While the intent of the question makes me cry, the difference between your two code snippets has nothing to do with do or eval and everything to do with context. And since that is a legitimate Perl topic, I'll briefly answer it.
In
my $tempref = eval $schemafile;
, the eval takes place in scalar context (imposed by the assignment to $tempref). $schemafile, however, contains a hash, created by the hash reference dereference operator %{}. When that hash is evaluated as a scalar it produces 1/8, normal behavior for a hash.
In
print Dumper do "/tmp/schemawithinschema.pl";
, the do takes place in the list context imposed by the Dumper call (which in turn is in the list context of the print). do creates the same hash that the eval did, but now it's being evaluated in list context, in fact as a list of arguments to Dumper. The top-level hash gets flattened into a list of Label => HashRef pairs, but that's not enough to stop Dumper from being able to show you something that looks a lot like the hash you were trying to create.
For future reference, it is helpful when trying to pinpoint a strange difference of behavior to present exactly the same call in both cases. The more variables between two test cases, the more things that you weren't expecting to matter will wind up mattering and confuse you.
All of that said, the real answer to "What's wrong here?" remains "Trying to do this at all.".