aturtle has asked for the wisdom of the Perl Monks concerning the following question:

I seem to have an array of hashes, I need to look up a each key => values in the hashes. I am not able to loop through each hash and find the key => values more than once. I know this is a very simple beginner problem and I have been reading stuff on Perl for days including: http://www.perlmonks.org/?node=References%20Quick%20Reference http://perldoc.perl.org/Data/Dumper.html and the all Perl books I have. What I am posting here is some code I whipped up that replicates the problem I am getting. I recreated the PR variable with the output of Data::Dumper in the real program, thats why I said 'seem to have an array of hashes' because I am not sure I am reading the output correctly but it matches what I get from this little bit of code. I created some variables that would be present from program. When I run this code I would expect HASH ELEM: to print the contents of each anonymous hash in the array not just the first one.
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; #test program #set up some test data: my $form; $form->{"id"} = '12594'; $form->{"cleared"} = '1'; print qq| ID: $form->{"id"} $form->{"cleared"}\n|; #set up what we seem to get from the web program #an array called $form->{PR} #another unnamed array called $VAR1 by data dumper? #var1 contains a list of hashes @{ $form->{PR} } = ( { 'source' => '8', 'name' => [ 'Telecom' ], 'description' => 'Telecom', 'entry_id' => 1583, 'transdate' => '08-10-2013', 'amount' => '-80', 'fx_transaction' => 0, 'id' => 10368, 'cleared' => 0 }, { 'source' => '', 'name' => [ 'iris' ], 'description' => 'iris', 'entry_id' => 1668, 'transdate' => '02-01-2014', 'amount' => '100', 'fx_transaction' => 0, 'id' => 12585, 'cleared' => 0 }, { 'source' => '12', 'name' => [ 'critter' ], 'description' => 'critter', 'entry_id' => 1670, 'transdate' => '02-01-2014', 'amount' => '12', 'fx_transaction' => 0, 'id' => 12592, 'cleared' => 0 }, { 'source' => '12', 'name' => [ 'critter' ], 'description' => 'critter', 'entry_id' => 1672, 'transdate' => '02-01-2014', 'amount' => '12', 'fx_transaction' => 0, 'id' => 12594, 'cleared' => 0 } ); #see if it loks right? print "PR:\n", Dumper $form->{PR}; #attempt look up a value: #my $elem ; foreach my $key ( @{ $form->{PR} } ) { if ( UNIVERSAL::isa( $key,'HASH') ) { #my %elem = %$key ; my %elem = %{$key} ; print "HASH ELEM:\n", Dumper %elem; foreach my $var ( keys %elem ) { if ($var->{id} == $form->{"id"}){ print qq|\t\telem->key $var->{id} eq $form->{"id"}\n|; print qq|\t\t cleared? $var->{cleared} $form->{"cleared"}\n| +; if ( $var->{cleared} != $form->{"cleared"} ){ $var->{cleared} = $form->{"cleared"}; print qq|\t\t FIXED $var->{cleared} $form->{"cleared"}\n|; } }#end if else {print qq|var id: $var->{id} ne form $form->{"id"}\n|; +} }#end loop array } if ( UNIVERSAL::isa( $key,'ARRAY') ) { my @elem = @$key ; #my @elem = $key ; #go down the columns print "ARRAY ELEM:\n", Dumper @elem; foreach my $var ( @elem ) { if ($var->{id} == $form->{"id"}){ print qq|\t\telem->key $var->{id} eq $form->{"id"}\n|; print qq|\t\t cleared? $var->{cleared} $form->{"cleared"}\n| +; if ( $var->{cleared} != $form->{"cleared"} ){ $var->{cleared} = $form->{"cleared"}; print qq|\t\t FIXED $var->{cleared} $form->{"cleared"}\n|; } }#end if else {print qq|var id: $var->{id} ne form $form->{"id"}\n|; +} }#end loop array } }#end for each elem print "done\n";
Thanks in advance

Replies are listed 'Best First'.
Re: how to loop each anonymous hash in an array of hashes
by tangent (Parson) on Mar 06, 2014 at 03:06 UTC
    You do indeed have an array of hashes (or rather an array of hash references), and to loop through them is actually quite easy:
    foreach my $var ( @{ $form->{PR} } ) { if ($var->{'id'} == $form->{'id'}) { print qq|elem->key $var->{id} eq $form->{"id"}\n|; print qq|\t\t cleared? $var->{cleared} $form->{"cleared"}\n|; if ( $var->{cleared} != $form->{"cleared"} ) { $var->{cleared} = $form->{"cleared"}; print qq|\t\t FIXED $var->{cleared} $form->{"cleared"}\n|; } } else { print qq|var id: $var->{id} ne form $form->{"id"}\n|; } }
    Output:
    var id: 10368 ne form 12594 var id: 12585 ne form 12594 var id: 12592 ne form 12594 elem->key 12594 eq 12594 cleared? 0 1 FIXED 1 1
Re: how to loop each anonymous hash in an array of hashes
by kcott (Archbishop) on Mar 06, 2014 at 08:57 UTC

    G'day aturtle,

    In order to get a better idea of how to write your code, perhaps the first thing to do would be to take a look at the general layout of your complete data structure (which is a hashref assigned to $form).

    { id => integer, cleared => boolean, PR => [ { ... }, ... { ... }, ], }

    Now that the structure is clear, you should be able to see how misleading the first line of your outer loop is:

    foreach my $key ( @{ $form->{PR} } ) {

    Each of the elements in @{ $form->{PR} } are hashrefs, not keys.

    You attempt to clarify this (by writing unnecessary code) with my %elem = %{$key}; however, you've possibly confused yourself even more because right below this you assign an actual key to another misleadingly named variable ($var) and then make the mistake of using it as a hashref (i.e. $var->{id}):

    foreach my $var ( keys %elem ) { if ($var->{id} == $form->{"id"}){

    A logical indentation of your code would make it easier to both read and maintain. I didn't find it easy to understand without close study (which shouldn't be necessary for fairly straightforward code such as this). This poor layout may well have been a contributing factor in some of the errors you've made. You can get some hints and tips on how to improve this from "perlstyle - Perl style guide"; you can use perltidy to reformat your code to your chosen style.

    I'd also recommend you read UNIVERSAL. In the SYNOPSIS, you'll see "but never do this!" followed by examples similar to usages in your code. The ref function would have been a better choice here; also see the reftype function in the built-in Scalar::Util module.

    Here's an example of how I might have searched through your data structure and made conditional changes to it:

    #!/usr/bin/env perl -l use strict; use warnings; use Data::Dumper; my $form = { id => 12594, cleared => 1, PR => [ { id => 10368, cleared => 0 }, { id => 12594, cleared => 0 }, ], }; print '*** BEFORE ***'; print Dumper $form; for my $hash_ref (@{$form->{PR}}) { if ($hash_ref->{id} == $form->{id}) { $hash_ref->{cleared} = $form->{cleared}; } } print '*** AFTER ***'; print Dumper $form;

    Output:

    *** BEFORE *** $VAR1 = { 'cleared' => 1, 'id' => 12594, 'PR' => [ { 'cleared' => 0, 'id' => 10368 }, { 'cleared' => 0, 'id' => 12594 } ] }; *** AFTER *** $VAR1 = { 'cleared' => 1, 'id' => 12594, 'PR' => [ { 'cleared' => 0, 'id' => 10368 }, { 'cleared' => 1, 'id' => 12594 } ] };

    [Also consider using Data::Dump. In many instances, it's a better choice than Data::Dumper.]

    -- Ken

Re: how to loop each anonymous hash in an array of hashes
by Anonymous Monk on Mar 06, 2014 at 03:37 UTC

    When I run this code I would expect HASH ELEM: to print the contents of each anonymous hash in the array not just the first one.

    Well, the error message you get is mighty informative :) even more so if you  use diagnostics;

    The error

    Can't use string ("source") as a HASH ref while "strict refs" in use a +t duck line 77 (#1) (F) Only hard references are allowed by "strict refs". Symbolic references are disallowed. See perlref. Uncaught exception from user code: Can't use string ("source") as a HASH ref while "strict refs" +in use at duck line 77.

    The error is referring to this code

    foreach my $var ( keys %elem ) { if ( $var->{id} == $form->{"id"} ) {
    Keys are always strings, they're never references, they're never hash references, so   $var->{id} doesn't make sense as $var is a key it is not %elem

    Here is what I would do

    #!/usr/bin/perl -- use strict; use warnings; use Data::Dump qw/ dd /; my $form = { PR => [ { cleared => 0, }, [], { deraelc => 1 }, { cleared => 0, }, \'yo', { deraelc => 1 }, ], }; for my $ref ( @{ $form->{PR} } ){ if( UNIVERSAL::isa( $ref,'HASH' ) ){ dd( HASH => $ref ); }elsif( UNIVERSAL::isa( $ref,'ARRAY' ) ){ dd( ARRAY => $ref ); }else{ dd( ELSE => $ref ); } } __END__

    Except replace  dd( $ref ); with doHashThing( $ref ); and doArrayThing( $ref ); ... respectively

    Then the first thing you do in doHashThing is dd()umper up the hash to see what you can see :) .... I'd even start a new file for each doThing until I'm comfortable with keys/hashes...