http://www.perlmonks.org?node_id=993192

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

Dear Monks,

If I have

my $animals = { gnu => { humps => 0, }, dromedary => { humps => 1, }, camel => { humps => 2, }, };

is there any simple way of extracting an array of the values for humps without explicitly traversing the top-level hashrefs?

Thanks,

loris

Replies are listed 'Best First'.
Re: Extracting values from nested hashrefs
by choroba (Cardinal) on Sep 12, 2012 at 09:13 UTC
    I am not sure whether map plus double values counts as implicit traversing:
    say for map values %$_, values %$animals;
    Update: simplified.
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Ah, sorry, my example was too simple. It should have been like this:

      my $animals = { gnu => { humps => 0, mascot_for => 'emacs', }, dromedary => { humps => 1, mascot_for => 'perl', }, camel => { humps => 2, mascot_for => 'perl', }, };

      BTW, implicit traversing is OK; I just wanted to avoid some sort of loop with , say while.

      Thanks,

      loris

        You can use
        map $_->{humps}, values %$animals;
        If your structure becomes even more complicated (e.g. different level of nesting), you should use recursion (or CPAN).
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

        Here is an approach that should work no matter how complicated your data structure becomes (provided the data you want remains in the form humps => d):

        #! perl use strict; use warnings; use Data::Dumper; my $animals = { gnu => { humps => 0, mascot_for => 'emacs', }, dromedary => { humps => 1, mascot_for => 'perl', }, camel => { humps => 2, mascot_for => 'perl', }, }; my $flat = Dumper($animals); print "$1\n" while $flat =~ / 'humps' \s+ => \s+ (\d+) /gx;

        Output:

        2 0 1

        TMTOWTDI.

        Athanasius <°(((><contra mundum

Re: Extracting values from nested hashrefs
by kcott (Archbishop) on Sep 12, 2012 at 09:25 UTC

    G'day loris,

    Hash keys have no inherent order. If you want to retain the order you've shown, you'll need to specify it explicitly.

    Here's two solutions in the one script. Pick whichever is most appropriate for you.

    #!/usr/bin/env perl use 5.010; use strict; use warnings; my $animals = { gnu => { humps => 0, }, dromedary => { humps => 1, }, camel => { humps => 2, }, }; my @humps; push @humps, $_->{humps} for map { $animals->{$_} } keys %$animals; say "Unsorted humps: @humps"; my @sorted_animals = qw{gnu dromedary camel}; my @sorted_humps; push @sorted_humps, $_->{humps} for map { $animals->{$_} } @sorted_ani +mals; say "Sorted humps: @sorted_humps";

    Outputs:

    $ pm_nested_hash_extract.pl Unsorted humps: 2 0 1 Sorted humps: 0 1 2

    -- Ken

      The ordering happens to be irrelevant, so the unsorted version is fine. I see from your solution that the distinction I was trying to make between explicit and implicit was a bit foolish. What I meant was that I wanted to do things in a nifty perl-like way, as you have done, rather than in a dull, C-like way.

      Thanks,

      loris

        Why be more cryptic that you need to be?
        Doing so will not be more efficient.
        #!/usr/bin/perl -w use strict; my $animals = { gnu => { humps => 0, mascot_for => 'emacs', }, dromedary => { humps => 1, mascot_for => 'perl', }, camel => { humps => 2, mascot_for => 'perl', }, }; foreach my $critter (keys %$animals) { print "$critter "; #camel gnu dromedary } print "\n"; foreach my $critter (keys %$animals) #sort if you want to { print "$animals->{$critter}{humps} "; #2 0 1 } print "\n";
Re: Extracting values from nested hashrefs
by trizen (Hermit) on Sep 12, 2012 at 13:39 UTC
    Using recursion:
    use 5.010; my $animals = { gnu => { humps => 0, mascot_for => 'emacs', }, dromedary => { humps => 1, zzz => { humps => 'nested', aaa => { humps => 'really nested', xxx => 0, }, }, mascot_for => 'perl', }, }; sub print_humps { my $hash_ref = shift; foreach my $key (keys %{$hash_ref}) { if (ref $hash_ref->{$key} eq "HASH") { print_humps($hash_ref->{$key}); } elsif ($key eq 'humps') { say $hash_ref->{$key}; } } } print_humps($animals); __END__ 0 1 nested really nested
Re: Extracting values from nested hashrefs
by tobyink (Canon) on Sep 12, 2012 at 14:38 UTC

    Here are four ways of doing it...

    use strict; use warnings; use Test::More; use B::Deparse; my $animals; my $deparse = B::Deparse->new; $deparse->ambient_pragmas(strict => 'all', warnings => 'all'); # Function to check that each solution works! sub this_works (&) { $animals = { gnu => { humps => 0, }, dromedary => { humps => 1, }, camel => { humps => 2, }, }; my @results = $_[0]->(); my $code = $deparse->coderef2text($_[0]); if (is_deeply [sort @results], [qw/ 0 1 2 /] ) { diag "this works $code"; } else { diag "FAILED! $code"; } } this_works { map($_->{humps}, values %$animals) }; this_works { map { $_->{humps} } values %$animals }; this_works { map { $animals->{$_}{humps} } keys %$animals }; this_works { require JSON::Path; JSON::Path->new('$..humps')->values($animals); }; done_testing();
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: Extracting values from nested hashrefs
by jdporter (Paladin) on Sep 12, 2012 at 13:58 UTC