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

Greetings monks. Although the problem is with a much larger hashref(media library information from my VLC player), I made this code for testing purposes but shares the same goal as my other code.
use strict; use warnings; my $hashref = { normal_enemies => { cloud_guy => { attacktype => 'thunder', power => 4, hp => 5, }, rain_man => { attacktype => 'water', power => 3, hp => 7, }, flame_kid => { attacktype => 'fire', power => 6, hp => 4, }, }, boss_enemies => { large_shark => { attacktype => 'water'. power => 9, hp => 13, special => 'none', }, wingding_monster => { attacktype => 'font'. power => 24, hp => 23, special => 'encrypt', }, green_dragon => { attacktype => 'fire'. power => 8, hp => 58, special => 'fire breath', }, }, items => { weapons => { sword_chucks => { power => 28, accuracy => 6, recommended_class => 'Crazy Fighter', }, cane => { power => 7, accuracy => 3, recommended_class => 'The Mage That Just Uses Magic. Y +ou know, if he ever needs to use this thing you\'re screwed anyway, r +ight?', }, katana => { power => 'over nine-thousand', accuracy => 8, recommended_class => 'Ninja Assassin', }, }, armor => { helm => { body_part => 'head', defense => 2.5, }, guantlets => { body_part => 'hands', defense => 1, }, necklace => { body_part => 'neck', defense => .1337, }, }, }, }; my %hash = %$hashref; print "Normal(Should fail with 'HASH(0xhex)' - $hashref\n"; print "Dereferenced - \n"; foreach (keys %hash) { print "$_ - $hash{$_}\n"; }
And the result is
Normal(Should fail with 'HASH(0xhex)' - HASH(0x99aae4) Dereferenced - normal_enemies - HASH(0x98a814) boss_enemies - HASH(0x99adc4) items - HASH(0x99ab24)
Now, what I want to do is take %hash deeper into the hashref. For example, how would I get a list of normal_enemies or the information from sword_chucks(this should come out as 'power - 28' instead of 'power - HASH(junk)') ? Thanks in advanced.

Replies are listed 'Best First'.
Re: getting keys of multi-level hashrefs
by Neighbour (Friar) on Jul 07, 2010 at 09:24 UTC
    This little sub will recursively dig through a supplied hashref and print all values.
    sub DigThroughHashref { my $hr_data = shift; my @keystack = @_; foreach (keys %{$hr_data}) { if (ref ($hr_data->{$_}) eq "HASH") { print ("Digging through [$_]:\n"); push (@keystack, $_); DigThroughHashref($hr_data->{$_}, @keystack); pop (@keystack); } else { print (join ('->', @keystack) . "->$_ - $hr_data->{$_}\n") +; } } ## end foreach (keys %{$hr_data}) } ## end sub DigThroughHashref
    Call it using DigThroughHashref($hashref);

    Also, there is an error in your boss_enemies data. There are periods following the attacktype instead of comma's.

    You can also use
    foreach (keys %{$hashref}) { }
    instead of declaring an extra %hash-variable.

    Also, you can get to certain values by simply using '->' a lot: printing $hashref->{items}->{armor}->{helm}->{body_part} will print 'head'.
    (This is basically what DigThroughHashref does).

    Edit: forgot ->$_ in the output

      When dealing with a large hash (the OP stated this was a small example) it may be better to use each rather than getting the overhead of a temporary list using keys. A skelton example:
      while (my ($key,$value) = each %$hashref) { print "$key $value\n"; }
        Though I doubt the overhead will have a significant impact, even with really large hashes, using each is indeed more optimized.
        That, and another optimization I thought of after posting my code, would change the subroutine to:
        sub DigThroughHashref { my $hr_data = shift; while (my ($key, $value) = each %{$hr_data}) { if (ref ($value) eq "HASH") { print ("Digging through [$key]:\n"); DigThroughHashref($value, @_, $key); } else { print (join ('->', @_) . "->$key - $value\n"); } } ## end while (my ($key, $value) ... } ## end sub DigThroughHashref
        All occurrances of $_ are now replaced by $key, and all occurrances of $hr_data->{$_} are now replaced by $value.
        Also, the seperate variable @keystack is now optimized out. Instead of pushing the to-be-parsed key explicitly onto the end of @keystack, it is now simply added to the arguments of the sub (which results in it being added to @_ inside the sub).
Re: getting keys of multi-level hashrefs
by jethro (Monsignor) on Jul 07, 2010 at 09:56 UTC

    Simple rule: if you have an (arbitrarily complex) variable that would print as HASH(0x...), surround it with %{ } and you can use that like any normal hash. Or add ->{key} after it, and you can access a value in it.

    In your example the hash in $hash{$_} would be usable as a hash with %{$hash{$_}} and any value in it you could access with $hash{$_}->{key}. If that value is a reference again, just apply this rule again.

Re: getting keys of multi-level hashrefs
by suhailck (Friar) on Jul 07, 2010 at 09:28 UTC
    perl -e 'my $hashref = { normal_enemies => { cloud_guy => { attacktype => 'thunder', power => 4, hp => 5, }, rain_man => { attacktype => 'water', power => 3, hp => 7, }, flame_kid => { attacktype => 'fire', power => 6, hp => 4, }, } };print map {$__=$_;join " ",$__,map {$_} keys %{$hashref->{ +$__}}} keys %$hashref'

    OUTPUT
    normal_enemies rain_man cloud_guy flame_kid
Re: getting keys of multi-level hashrefs
by youwin (Beadle) on Jul 07, 2010 at 22:11 UTC
    Heres one way of doing it:
    my @normal_enemies = keys %{$hashref->{normal_enemies}}; print "normal enemies:\n@normal_enemies\n\n"; my %chucky = %{$hashref->{items}{weapons}{sword_chucks}}; print "sword chucks:\n"; for (keys %chucky) { print "$_ - $chucky{$_}\n"; }