2010-03-02 47 views
16

我有哈希密钥的值是其他散列。如何在Perl中迭代Hash(Hashes)?

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

我如何通过这种结构循环?

+1

你可以举一个更现实的例子。你在哪里遇到这样的结构?它是干什么用的?您想做什么? 也许另一个数据结构会更适合这项任务? – Aurril 2010-03-02 13:04:19

+0

@Aurril:嵌套散列结构对许多事情都很有用,请参阅下面我的帖子中的链接。 – Zaid 2010-03-02 13:13:24

+0

每个散列都有多个键吗? – Svante 2010-03-02 13:22:32

回答

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... 
}; 
+1

我喜欢你的Yoda条件在第四行:) – 2013-08-05 08:08:22

0
foreach my $keyname (keys(%foo) { 
    my $subhash = $foo{$keyname}; 
    # stuff with $subhash as the value at $keyname 
} 
+1

这应该是$ foo {$ keyname}而不是%foo {$ keyname}! – 2010-03-02 13:06:33

+0

所以它应该。这就是我在咖啡之前发布的内容。 – monksp 2010-03-02 13:12:15

0

你将不得不循环两次。即

while (($family, $roles) = each %HoH) { 
    print "$family: "; 
    while (($role, $person) = each %$roles) { 
     print "$role=$person "; 
    } 
print "\n"; 
} 
7

This post可能是有用的。

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 
      # . 
      # . 
      # . 
     } 
    } 
} 
+0

在OP中,数据结构的第一个括号是大括号,表示它是散列引用。我的$ hash = {'key'=> {'key2'=> {'key3'=>'value'}}} 因此您需要解除引用 – ccheneson 2010-03-02 13:20:16

+1

此解决方案仅适用于存在定义的固定数量的子哈希。如果哈希结构是自动生成的,那么您需要更通用的方法。递归算法将是更好的解决方案。 我不熟悉Perl,否则我会举一个例子。 – Aurril 2010-03-02 13:33:12

+0

@ccheneson:无需取消引用。就是这样。 – Zaid 2010-03-02 13:46:16

23

这个答案建立在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); 
+0

这帮了我很多,谢谢 – 2010-03-03 13:39:33

7

较早的答案显示如何推出自己的解决方案,这是很好的做至少一次,以便您了解如何perl的引用和数据结构工作的胆量。如果您尚未阅读,您应该仔细阅读perldoc perldscperldoc perlref

但是,您不需要编写自己的解决方案 - CPAN上已经有一个模块,它将通过任意复杂的数据结构迭代:Data::Visitor

+0

+1谢谢,'Data :: Visitor'看起来很有用。从文档看,如何做一些简单的事情并不是很明显 - 例如,遍历嵌套的散列结构,打印叶节点值和它们的键(直接和他们的祖先)。我相信这是可行的。只需要将我的头包裹一下就可以了。 :) – FMc 2010-03-02 20:05:18

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"; 
} 
0

如果您正在使用Perl作为一个 “CPAN解释” 那么除了Data::Visitor和​​有超级简单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; 

输出

t => 10 
y => 11 

$a$b被视为特殊变量在这里(和sort()一样),而在traverse()函数中。 Data::Traverse是一个非常简单但非常有用的模块,没有非CORE依赖关系。