How do I reference a Perl hash in an array in a hash?

20,533

Solution 1

Here is some updated code, with an explanation below:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my %photo_details = (
    'black_cat' => [
        {'size' => '1600x1200', 'position' => -25},
        {'size' => '1280x1024', 'position' =>  25},
        {'size' =>   '800x600', 'position' =>   0},
    ],
    'race_car' => [
        {'size' => '1600x1200', 'position' =>  10},
        {'size' =>   '800x600', 'position' =>   5},
    ],
);


print Dumper( %photo_details );
foreach my $name ( keys %photo_details ) {
    foreach my $photo_detail ( @{ $photo_details{$name} } ) {
        my $size     = $photo_detail->{'size'};
        my $position = $photo_detail->{'position'};

        print Dumper( $photo_details{$photo} );

        print ("size = $size, position = $position\n");
    }
}

I've replaced some of your parentheses with square and curly brackets. In Perl, square brackets give you a reference to an anonymous array, and curly brackets denote a reference to an anonymous hash. These are called anonymous because there's no explicit variable name for the anonymous array or hash.

As Perl data structures make you store a reference to a hash rather than the actual hash, you need these to construct the references. You can do this in two steps like this:

my @array = ( 1, 2, 3 );
my $array_ref = \@array;
my %hash = ( 'one' => 1, 'two' => 2, 'three' => 3 );
my $hash_ref = \%hash_ref;

To get data out of $array_ref and $hash_ref, you need the -> operator:

print $array_ref->[0], "\n";
print $hash_ref->{one}, "\n";

You don't need the quotes inside of the {} when referencing a hash key, although some people consider quotes on a hash key to be good practice.

I added an example of iteration over the entire data structure as an example rather than just looking at one reference. Here's the first line:

foreach my $name ( keys %photo_details ) {

The keys method returns all of the keys in a hash, so that you can get them in order. The next line iterates over all of the photo_detail hashrefs in %photo_details:

    foreach my $photo_detail ( @{ $photo_details{$photo} } ) {

The @{ $photo_details{$photo} } de-references the reference $photo_details{$photo} into an array, which you can iterate over it with foreach.

The last thing that I added is a call to Data::Dumper, a very useful module distributed with Perl that prints out data structures for you. This is very handy when building up data structures like this, as is its closely related cousin Data::Dumper::Simple. This module is unfortunately not distributed with Perl, but I prefer its output as it includes variable names.

For some further reading about how to build up complex data structures using references, check out perlreftut.

Solution 2

First of all, always start every script or module with:

use strict;
use warnings;

You will get more warning messages and sooner, which greatly helps debugging.

I cannot duplicate your error: when I put that code into a file and run it with no additional flags, I get: size = , position =. There is no $size variable in the code you printed, so the error message does not match.

Nevertheless, you are declaring your data structures incorrectly. Hashes and arrays can only contain scalar values, not lists: so if you want to nest an array or a hash, you need to make it a reference. See perldoc perldata, perldoc perldsc and perldoc perlreftut for more about data structures and references.

my %photo_details = (
    black_cat => [
        { size => '1600x1200', position => -25 },
        { size => '1280x1024', position =>  25 },
        { size => '800x600', position => 0 },
    ],
    race_car => [
        { size => '1600x1200', position =>  10 },
        { size => '800x600', position =>   5 },
    ],  
);

foreach my $photo_detail (@{$photo_details{black_cat}})
{
    my $size     = $photo_detail->{size};
    my $position = $photo_detail->{position};

    print ("size = $size, position = $position\n");
}

Solution 3

There's really only one thing you have to worry about, and that's the top level of the data structure. After that, you just use the right indexing syntax for each level:

If you have a regular hash, you access the key that you want then line up the additional indices for each level after it:

 %regular_hash = ...;
 $regular_hash{$key}[$index]{$key2};

If you have a reference, you do almost the same thing, but you have to start off with the initial dereference with an arrow, ->, after the top-level reference. After that it's the same indexing sequence:

 $hash_ref = ...;
 $hash_ref->{$key}[$index]{$key2};

For all of the details, see Intermediate Perl where we explain reference syntax.

Share:
20,533
TallGuy
Author by

TallGuy

Software developer located in Wellington, New Zealand. Occasional movie star (we make a few around here). Tall.

Updated on August 15, 2020

Comments

  • TallGuy
    TallGuy over 3 years

    This is the code snippet I am working with:

    my %photo_details = (
     'black_cat' => (
      ('size' => '1600x1200', 'position' => -25),
      ('size' => '1280x1024', 'position' =>  25),
      ('size' =>   '800x600', 'position' =>   0),
     ),
     'race_car' => (
      ('size' => '1600x1200', 'position' =>  10),
      ('size' =>   '800x600', 'position' =>   5),
     ),
    );
    
    my $photo = 'black_cat';
    
    foreach my $photo_detail ($photo_details{$photo})
    {
     my $size     = $photo_detail{'size'};
     my $position = $photo_detail{'position'};
    
     print ("size = $size, position = $position\n");
    }
    

    What I am expecting to get is:

    size = 1600x1200, position = -25

    size = 1280x1024, position = 25

    size = 800x600, position = 0

    What I do get is:

    Use of uninitialized value $size in concatenation (.) or string at C:\Test.pl line 23.

    Use of uninitialized value $position in concatenation (.) or string at C:\Test.pl line 23.

    size = , position =

    The foreach statement is clearly wrong as not only are there no values for $size and $position, it has only gone through the loop once instead of three times. I have tried all sorts of variants of variable prefixes and found none that work.

    What am I doing wrong?