Jak iterować poprzez Hash (z hashów) w Perlu?

Mam Hash, gdzie wartości kluczy są innymi Hashami.

Przykład: {'key' => {'key2' => {'key3' => 'value'}}}

Jak mogę iterować przez tę strukturę?

Author: Axeman, 2010-03-02

9 answers

Tego chcesz? (untested)

sub for_hash {
    my ($hash, $fn) = @_;
    while (my ($key, $value) = each %$hash) {
        if ('HASH' eq ref $value) {
            for_hash $value, $fn;
        }
        else {
            $fn->($value);
        }
    }
}

my $example = {'key' => {'key2' => {'key3' => 'value'}}};
for_hash $example, sub {
    my ($value) = @_;
    # Do something with $value...
};
 12
Author: dave4420,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 13:10:10

Ta odpowiedź opiera się na idei Dave ' a Hintona-mianowicie, aby napisać podprogram ogólnego przeznaczenia, aby przejść strukturę haszującą. Taki hash walker pobiera odniesienie do kodu i po prostu wywołuje ten kod dla każdego węzła liścia w hashu.

Przy takim podejściu, ten sam hash walker może być używany do wielu rzeczy, w zależności od tego, które wywołanie zwrotne mu podamy. Aby uzyskać jeszcze większą elastyczność, musisz przekazać dwa wywołania zwrotne - jedno do wywołania, gdy wartość jest odwołaniem hash, a drugie do wywołaj, gdy jest zwykłą wartością skalarną. Strategie takie jak ta zostały dokładniej zbadane w doskonałej książce marca Jasona Dominusa, Perl wyższego rzędu.

use strict;
use warnings;

sub hash_walk {
    my ($hash, $key_list, $callback) = @_;
    while (my ($k, $v) = each %$hash) {
        # Keep track of the hierarchy of keys, in case
        # our callback needs it.
        push @$key_list, $k;

        if (ref($v) eq 'HASH') {
            # Recurse.
            hash_walk($v, $key_list, $callback);
        }
        else {
            # Otherwise, invoke our callback, passing it
            # the current key and value, along with the
            # full parentage of that key.
            $callback->($k, $v, $key_list);
        }

        pop @$key_list;
    }
}

my %data = (
    a => {
        ab => 1,
        ac => 2,
        ad => {
            ada => 3,
            adb => 4,
            adc => {
                adca => 5,
                adcb => 6,
            },
        },
    },
    b => 7,
    c => {
        ca => 8,
        cb => {
            cba => 9,
            cbb => 10,
        },
    },
);

sub print_keys_and_value {
    my ($k, $v, $key_list) = @_;
    printf "k = %-8s  v = %-4s  key_list = [%s]\n", $k, $v, "@$key_list";
}

hash_walk(\%data, [], \&print_keys_and_value);
 23
Author: FMc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 18:31:16

Ten post może być przydatny.

foreach my $key (keys %hash) {
    foreach my $key2 (keys %{ $hash{$key} }) {
        foreach my $key3 (keys %{ $hash{$key}{$key2} }) {
            $value = $hash{$key}{$key2}->{$key3};
            # .
            # .
            # Do something with $value
            # .
            # .
            # .
        }
    }
}
 7
Author: Zaid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:00

Wcześniejsze odpowiedzi pokazują, jak wykonać własne rozwiązanie, co warto zrobić przynajmniej raz, aby zrozumieć, jak działają odniesienia i struktury danych w Perlu. Zdecydowanie powinieneś przeczytać perldoc perldsc i perldoc perlref, Jeśli jeszcze tego nie zrobiłeś.

Nie musisz jednak pisać własnego rozwiązania - istnieje już moduł na CPAN, który będzie iteracją przez dowolnie złożone struktury danych: Data::Visitor .

 7
Author: Ether,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 19:20:38

Proszę również przeczytać perldoc perldsc. Możesz dowiedzieć się o hashach w głębi

 5
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 13:01:20

To nie jest tak naprawdę nowa odpowiedź, ale chciałem się podzielić, jak zrobić więcej niż wystarczy wypisać rekurencyjnie wszystkie wartości skrótu, ale także zmodyfikować je w razie potrzeby.

Oto moja tak drobna modyfikacja odpowiedzi dave4420, w której wartość jest przekazywana do wywołania zwrotnego jako odniesienie, więc moje wywołanie zwrotne wtedy funkcja może zmodyfikować każdą wartość w hash.

Musiałem też przebudować hash, gdy każda pętla tworzy kopie nie referencje.

sub hash_walk {
   my $self = shift;
    my ($hash, $key_list, $callback) = @_;
    while (my ($k, $v) = each %$hash) {
        # Keep track of the hierarchy of keys, in case
        # our callback needs it.
        push @$key_list, $k;

        if (ref($v) eq 'HASH') {
            # Recurse.
            $self->hash_walk($v, $key_list, $callback);
        }
        else {
            # Otherwise, invoke our callback, passing it
            # the current key and value, along with the
            # full parentage of that key.
            $callback->($k, \$v, $key_list);
        }

        pop @$key_list;
        # Replace old hash values with the new ones
        $hash->{$k} = $v;
    }
}

hash_walk(\%prj, [], \&replace_all_val_strings);

sub replace_all_val_strings {
    my ($k, $v, $key_list) = @_;
    printf "k = %-8s  v = %-4s  key_list = [%s]\n", $k, $$v, "@$key_list";
    $$v =~ s/oldstr/newstr/;
    printf "k = %-8s  v = %-4s  key_list = [%s]\n", $k, $$v, "@$key_list";
}
 1
Author: stephenmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-29 17:43:05

Będziesz musiał przejść przez nią dwa razy. tj.

while ( ($family, $roles) = each %HoH ) {
   print "$family: ";
   while ( ($role, $person) = each %$roles ) {
      print "$role=$person ";
   }
print "\n";
}
 0
Author: Marcos Placona,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 12:51:30
foreach my $keyname (keys(%foo) {
  my $subhash = $foo{$keyname};
  # stuff with $subhash as the value at $keyname
}
 0
Author: monksp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 13:11:59

Jeśli używasz Perla jako "interpretera CPAN" to oprócz Data::Visitor oraz Data::Deep jest super proste Data::Traverse:

use Data::Traverse qw(traverse);

my %test_hash = (
  q => [qw/1 2 3 4/],
  w => [qw/4 6 5 7/],
  e => ["8"],
  r => { 
         r => "9"  ,
         t => "10" ,
         y => "11" ,
      } ,
);

traverse { next if /ARRAY/; print "$a => $b\n" if /HASH/ && $b > 8 } \%test_hash;

Wyjście :

t => 10
y => 11

$a i {[6] } są tu traktowane jako zmienne specjalne (jak w przypadku sort()), podczas gdy wewnątrz funkcji traverse(). Data::Traverse jest bardzo prostym, ale niezwykle użytecznym modułem bez żadnych innych zależności.

 0
Author: G. Cito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-11 20:15:16