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

I have the following hash:
$hash{A}{B}{C}{D}{one} = "1"; $hash{A}{B}{C}{D}{two} = "2"; $hash{A}{B}{C}{D}{thr} = "3"; $hash{A}{B}{C}{E}{one} = "4"; $hash{A}{B}{C}{E}{two} = "5"; $hash{A}{B}{C}{E}{thr} = "6";
I would like to be able to sort through both "D" and "E" at the same time ... something along the lines of:
foreach my $key (sort {$a cmp $b} keys "D", "E") { print "$hash{$key}"; }
that is, I want to sort the keys of hash_ref D and E, then use the value of $hash{A}{B}{C}{D}{$key} || $hash{A}{B}{C}{E}{$key} depending on which it came from.

I tried the following code, as a starting point, but the results are unexpected.
#!/usr/bin/perl -w use strict; my %hash; $hash{A}{B}{C}{D}{one} = "1"; $hash{A}{B}{C}{D}{two} = "2"; $hash{A}{B}{C}{D}{thr} = "3"; $hash{A}{B}{C}{E}{one} = "4"; $hash{A}{B}{C}{E}{two} = "5"; $hash{A}{B}{C}{E}{thr} = "6"; foreach my $key (keys %{$hash{A}{B}{C}{E}}, %{$hash{A}{B}{C}{D}}) { print "$key\n"; }
the results that I get from this are:
thr one two thr 3 one 1 two 2
And even so, I am not sure how to know which hash_ref "$key" is currently coming from.

Replies are listed 'Best First'.
Re: working with multiple hash refs
by sauoq (Abbot) on Dec 19, 2002 at 21:10 UTC

    This will get you part of the way there...

    for my $key (sort keys %{$hash{A}{B}{C}{D}}, keys %{$hash{A}{B}{C}{E}) + { print $key,"\n"; }
    As for knowing which hash the key came from, that's a bit more difficult and your request is actually ambiguous. What do you want to do about keys which are in both hashes?

    You will probably need to extract the keys into another data structure. You could, for instance, use an array of array references where each reference refers to an array of two elements. The first would be the key and the second would be either 'D' or 'E'.

    Something like this (untested) code:

    my @allkeys; push @allkeys, [$_, 'D'] for keys %{$hash{A}{B}{C}{D}}; push @allkeys, [$_, 'E'] for keys %{$hash{A}{B}{C}{E}}; for my $ref (sort {$a->[0] cmp $b->[0]} @allkeys) { my $key = $ref->[0]; my $which = $ref->[1]; print "$key from $which hash.\n"; }

    Edit: Changed $key to $_ on the "push ... for" lines. ++tachyon for spotting the error.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: working with multiple hash refs
by tachyon (Chancellor) on Dec 19, 2002 at 21:28 UTC

    Something like this does the trick. You need another data structure to remember which hash a particular key comes from. You have to use an array as if you use a hash you will stomp on duplicate keys. If you want to sort on the 'D' and 'E' fields as well you would just add the extra sort in the usual way:

    $hash{A}{B}{C}{D}{one} = "1"; $hash{A}{B}{C}{D}{two} = "2"; $hash{A}{B}{C}{D}{thr} = "3"; $hash{A}{B}{C}{E}{one} = "4"; $hash{A}{B}{C}{E}{two} = "5"; $hash{A}{B}{C}{E}{thr} = "6"; my @sort; push @sort, [ $_, 'D' ] for keys %{$hash{A}{B}{C}{D}}; push @sort, [ $_, 'E' ] for keys %{$hash{A}{B}{C}{E}}; for my $ref ( sort { $a->[0] cmp $b->[0] } @sort ) { print "Key: $ref->[0]; Hash: $ref->[1]\n"; } __DATA__ Key: one; Hash: D Key: one; Hash: E Key: thr; Hash: D Key: thr; Hash: E Key: two; Hash: D Key: two; Hash: E

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: working with multiple hash refs
by jdporter (Paladin) on Dec 19, 2002 at 21:41 UTC
    As sauoq said, the question is ambiguous, because you haven't said how you want to handle the case where the same key appears under both D and E -- and your sample hash presents exactly that situation!
    But assuming you want to print out all the data, regardless of such duplication,
    what I would do is make a new hash that essentially inverts the lowest two levels of the given hash. Like so:
    my %h; # the inverted hash for my $de ( keys %{ $hash{A}{B}{C} } ) { # gives D, E for my $k ( keys %{ $hash{A}{B}{C}{$de} } ) { $h{$k}{$de} = $hash{A}{B}{C}{$de}{$k}; } } # now print it out: for my $k ( sort keys %h ) { for my $de ( sort keys %{$h{$k}} ) { print "$k = $h{$k}{$de} (from $de)\n"; } }

    jdporter
    ...porque es dificil estar guapo y blanco.

      That is very broken. If a value appears more than once in the two hashes, then your code will discard all but one of the keys it is associated with. You can't just invert the hashes unless you know that all of the values are unique (or you don't care about losing data.)

      Update: I was wrong. As jdporter explains below, he is not using values as keys. My apologies.

      -sauoq
      "My two cents aren't worth a dime.";
      
        (Somehow I don't feel that posting nodes in a thread is the best way to have this kind of debate, but...)

        I don't know how to tell you gently that you are quite mistaken.
        I am only inverting two levels of keys of hashes. The values stay at the "leafs", where they were.

        In other words, given hash %a as     $a{foo}{bar} = value; I am creating an "inversion" of that hash, %b, as     $b{bar}{foo} = value; No potential for loss. No values being used as keys, nor vice versa.

        jdporter
        ...porque es dificil estar guapo y blanco.