2010-03-02 56 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依賴關係。