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

Hi Monks,

I used to do something like the following at a place of work over a year ago and I just forget the syntax. It's driving me kind of crazy. What I used to have the need to often do is have a hash of hashes data structure. Inner hashes would be named according to a string value that was read from a flat file.

What I would also do is call subroutines and pass the inner hash by reference. And I just don't remember the syntax for doing so. If someone out there would be kind enough to divulge the correct syntax for creation of the inner hash and for passing it by reference to a subroutine as well as WHY that syntax is correct, I'd sure be obliged!

I made up the following application. I have this flat file with a few entries. In order are a name,species,gender,age, and hair color.

I have an outer hash and I want the inner hash to contain the value for name. The inner hashes keys would be species, gender, age, etc.

For the sake of learning the syntax, I want to loop through the flat file and create each inner hash with each iteration. Anyway, here is the contents of the text file:

Tony,human,male,47,gray Mark,human,male,48,brown Tyler,dog,male,11,brown Anthi,human,female,39,black Spud,cat,male,14,white Erica,human,female,23,blonde
and here is the code I presently have (I know it's silly, but I am passing everything by reference):
#!/usr/bin/perl -w use strict; my %myOuterHash = (); open (INFO,"<info.txt"); while (<INFO>) { my ($name,$species,$gender,$age,$hairColor) = split /,/,$_; %$myOuterHash{ $name } = (); addValues( \%$myOuterHash{ $name },\$species,\$gender,\$age,\$hair +Color); } sub addValues { my ( $hashNameRef,$speRef,$genRef,$ageRef,$hairColRef ) = @_; $$hashNameRef{ 'species' } = $$speRef; print "IN SUB:\t$$hashNameRef{ 'species' }\n"; }
One other thing I was wondering. In my application at my former place of employment, I had many records for the same inner hash. So, I did not create that inner hash until I first checked to see if it already was created. Might you also supply the syntax for this conditional?

Thanks!,

Tony (o2)

Replies are listed 'Best First'.
Re: Syntax for Hashes of Hashes
by fmerges (Chaplain) on Jul 24, 2005 at 18:42 UTC

    Hi,

    my %g = (); while(<INFO>) { my ($name,$specie,$gender,$age,$hairColor) = split /,/,$_; $g{$name} = { SPECIE => $specie, GENDER => $gender, AGE => $age, HAIRCOLOR => $hairColor, }; }

    To pass the inner hash as a reference to a subroutine you can simply say:

    call_sub( $g{$name} );

    That's because the value of the hash key is actually a reference to a hash.

    But in this case I wouldn't use the $name as the key, because that way you can't have 2 people with the same name... Better to use a unique field, or an id number...

    And then other interesting things like OO and DB... ;-)

    Regards,

    |fire| at irc.freenode.net

    I like merlyn's disclaimer

      Hey Fire,

      Thanks!

      Two questions...

      1. What if you want to create the inner hash without setting any value to it during its creation? How do you "inform" that it is a hash that is being created?

      What is the conditional for seeing if it exists? Is it?

      if !($g{$name})
      Thanks again...

      Tony (o2)
        To define an inner hash without contents, you can do:
        $g{$name} = {};
        to check for existence, you do:
        if ( exists($g{$name} ) { }
        Otherwise, the way you were doing it, you are only checking to see if the value of the key is defined with a true value.

        As a matter of fact there is no need to for you to create inner hash if you don't need it RIGHT AT THE MOMENT. it will be created at the time you want it. For example:

        use Data::Dumper; use strict; use warnings; my $h = {}; $h->{"a"}{"b"}{"c"} = 1; print Dumper($h);

        This works, and there is no need to create level a first, then level b, then assign level c. Level a and b magically come to existance.

Re: Syntax for Hashes of Hashes
by davidrw (Prior) on Jul 24, 2005 at 20:13 UTC
    In the spirit of TWTOWTDOI, a variant of fmerges's solution to use hash slices:
    my %g = (); my @innerKeys = qw/ specie gener age haircolor /; while(<INFO>) { my @vals = split /,/, $_; my $name = shift @vals; @{$g{$name}}{ @innerKeys } = @vals; }

    On a related note, I happened to just now be working with similar code that additionally leverages Text::CSV so that you could have a line like "Mr. Jones, Jr.",human,male,47,gray
    my $csv = Text::CSV->new(); open FILE, $filename or die "couldn't open file '$filename': $!"; # if the file has colnames in the first row, do this: $_ = <FILE>; $csv->parse($_); my @cols = $csv->fields(); # otherwise, manually define them: my @cols = qw/ name specie gener age haircolor /; while(<FILE>){ # let Text::CSV do the hard work. $csv->parse($_); # this turns blanks into undef's -- remove the "map {}" part if bl +anks are ok. my @vals = map { !defined($_) || $_ eq '' ? undef : $_ } $csv->fie +lds(); # This hashes the whole row my $row = { map { $cols[$_] => $vals[$_] } 0 .. $#cols }; # Now do whatever you need with it. $g{ $row->{name} } = $row; # Or a common/generic usage might be: push @rows, $row; }