views:

269

answers:

3

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?

+12  A: 

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");
}
Ether
Good advice and explanation. All I would add is that the following is useful in debugging Perl data structure issues: use Data::Dumper; print Dumper(\%photo_details);
toolic
I'm confused, $size definitely **is** in the snippet he posted, you even carried it over to your code. What did you mean?
lexu
@lexu: yes, you're right; I was thinking the error might have come from using `$photo_detail->{$size}` vs `$photo_detail->{size}`. I totally overlooked the `my $size = ...` declaration. Nevertheless, I can't duplicate the OP's error. With strictures enabled, the code barfs earlier, at `%photo_detail` not being declared (due to the for loop not dereferencing the array).
Ether
Thanks for the explanation. The data structure definitely looks right now. However, if I try the code above I get "Global symbol "%photo_detail" requires explicit package name" on line 20 and 21. I read the docs (thanks for that) and changed the foreach statement to `[foreach my $photo_detail (@{%photo_details->{black_cat}})], changed `[$photo_detail{size}] to `[$photo_detail->{size}] and changed `[$photo_detail{position}] to `[$photo_detail->{position}] and it works but I get a "Using a hash as a reference is deprecated" on the foreach line. Any ideas?
TallGuy
@Ether I wasn't faulting your logic, only the statement abaut $size threw me of! The OP seems to be learning about the use of sigils and scopes .. both are confusing at first. (+1 from me for your good advice)
lexu
Stupid comment mark up. Hope you can understand what I meant...
TallGuy
@TallGuy: looking up that warning message in 'perldoc perlwarn' is informative: you have a punctuation error on the foreach line.
Ether
+6  A: 

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{$photo} } ) {
        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.

James Thompson
+2  A: 

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.

brian d foy