17

我有哈希,其中键的值是其他哈希。

例子:{'key' => {'key2' => {'key3' => 'value'}}}

我怎样才能遍历这个结构?

4

8 回答 8

24

这个答案建立在 Dave Hinton 背后的想法之上——即编写一个通用的子例程来遍历哈希结构。这样的哈希遍历器获取代码引用并简单地为哈希中的每个叶节点调用该代码。

使用这种方法,同一个哈希遍历器可以用来做很多事情,这取决于我们给它的回调。为了获得更大的灵活性,您需要传递两个回调——一个在值是散列引用时调用,另一个在它是普通标量值时调用。Marc Jason Dominus 的优秀著作《 Higher Order Perl》对此类策略进行了更深入的探讨。

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);
于 2010-03-02T14:07:45.337 回答
12

这是你想要的吗?(未经测试)

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...
};
于 2010-03-02T13:10:10.233 回答
7

这篇文章可能有用。

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
            # .
            # .
            # .
        }
    }
}
于 2010-03-02T13:09:49.657 回答
7

较早的答案显示了如何推出自己的解决方案,最好至少做一次,以便您了解 perl 引用和数据结构如何工作的核心。如果您还没有阅读perldoc perldscperldoc perlref ,那么您绝对应该通读一遍。

但是,您不需要编写自己的解决方案——CPAN 上已经有一个模块可以为您遍历任意复杂的数据结构:Data::Visitor

于 2010-03-02T19:20:38.407 回答
1

这并不是一个真正的新答案,但我想分享如何做更多的事情,而不仅仅是递归地打印所有哈希值,还可以在需要时修改它们。

这是我对 dave4420 答案的微小修改,其中值作为参考传递给回调,因此我的回调例程可以修改散列中的每个值。

我还必须重建哈希,因为每个循环创建副本而不是引用。

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";
}
于 2011-07-29T17:43:05.653 回答
1

如果您使用 perl 作为“CPAN 解释器”,那么除了Data::Visitor 超级Data::Deep简单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 { return if /ARRAY/; print "$a => $b\n" if /HASH/ && $b > 8 } \%test_hash;

输出

t => 10
y => 11

$a并且在函数内部$b被视为特殊变量(与 一样sort()) 。是一个非常简单但非常有用的模块,没有非核心依赖项。traverse()Data::Traverse

于 2015-11-10T19:18:20.503 回答
0
foreach my $keyname (keys(%foo) {
  my $subhash = $foo{$keyname};
  # stuff with $subhash as the value at $keyname
}
于 2010-03-02T12:49:02.390 回答
0

您将不得不循环两次。IE

while ( ($family, $roles) = each %HoH ) {
   print "$family: ";
   while ( ($role, $person) = each %$roles ) {
      print "$role=$person ";
   }
print "\n";
}
于 2010-03-02T12:51:30.873 回答