2010-03-02 20 views
16

Ho Hash dove i valori delle chiavi sono altri Hash.Come eseguire iterazioni su Hash (di Hash) in Perl?

Esempio: {'key' => {'key2' => {'key3' => 'value'}}}

Come posso iterare attraverso questa struttura?

+1

Potrebbe fornire un esempio più realistico. Dove incontri una tale struttura? A cosa serve? Che cosa vuoi fare? Forse un'altra struttura dati sarebbe più appropriata per l'attività? – Aurril

+0

@Aurril: le strutture hash annidate sono utili per molte cose, vedi il link nel mio post qui sotto per un esempio. – Zaid

+0

Ogni hash individuale ha più di una chiave? – Svante

risposta

12

È questo quello che vuoi? (Non testato)

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

Mi piace la tua condizione Yoda nella quarta riga :) –

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

Questo dovrebbe essere $ foo {$ keyname} not% foo {$ keyname}! –

+0

Quindi dovrebbe. Questo è quello che ottengo per la pubblicazione prima del caffè. – monksp

0

È necessario eseguire il ciclo due volte. Ad esempio

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

This post può essere utile.

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

Nell'OP, le prime parentesi della struttura dati sono parentesi graffe che indica che si tratta di un riferimento hash. my $ hash = {'key' => {'key2' => {'key3' => 'valore'}}} Quindi dovrai dereferenziare – ccheneson

+1

Questa soluzione funziona solo se esiste un numero fisso definito di subhashes . Se la Hashstructure viene generata automaticamente, è necessario un approccio più generico. Un Algoritmo ricorsivo sarebbe una soluzione migliore. Non ho familiarità con Perl, altrimenti darei un esempio. – Aurril

+0

@ccheneson: non c'è bisogno di dereferenziare. È quello che è. – Zaid

23

Questa risposta si basa su l'idea dietro Dave Hinton di - vale a dire, scrivere una subroutine general purpose a camminare una struttura hash. Tale hash walker prende un riferimento al codice e chiama semplicemente quel codice per ogni nodo foglia nell'hash.

Con tale approccio, lo stesso hash walker può essere utilizzato per fare molte cose, a seconda del callback che gli diamo. Per una flessibilità ancora maggiore, è necessario passare due callback: uno da invocare quando il valore è un riferimento hash e l'altro da richiamare quando è un valore scalare ordinario. Strategie come questa sono esplorate in modo più approfondito nell'eccellente libro di 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

Questo mi ha aiutato molto, grazie –

7

Le risposte precedenti mostrano come rotolare la vostra soluzione, che è bene fare almeno una volta in modo da capire il coraggio di come perl riferimenti e strutture dati di lavoro. Se non l'hai già fatto, dovresti assolutamente leggere i numeri perldoc perldsc e perldoc perlref.

Tuttavia, non è necessario scrivere la propria soluzione: esiste già un modulo su CPAN che eseguirà automaticamente delle strutture di dati arbitrariamente complesse: Data::Visitor.

+0

+1 Grazie, 'Data :: Visitor' sembra utile. Non è stato immediatamente evidente dai doc come fare qualcosa di semplice - ad esempio, attraversando una struttura hash annidata, stampando i valori delle foglie e le loro chiavi (immediate e i loro antenati). Sono sicuro che sia fattibile; ho solo bisogno di avvolgere la mia testa per un po '. :) – FMc

1

Questa non è davvero una nuova risposta ma volevo condividere come fare più di stampare tutti i valori hash in modo ricorsivo ma anche modificarli se necessario.

Ecco il mio mai così leggera modifica della risposta del dave4420 in cui il valore viene passato al callback come riferimento così la mia routine di callback potrebbe quindi modificare ogni valore di hash.

Ho anche dovuto ricostruire l'hash mentre ogni ciclo crea copie non riferimenti.

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

Se si utilizza Perl come un "interprete CPAN", quindi oltre a Data::Visitor e Data::Deep c'è il super semplice 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; 

uscita:

t => 10 
y => 11 

$a e $b sono trattati come variazione speciale bles qui (come con sort()) all'interno della funzione traverse(). Data::Traverse è un modulo molto semplice ma estremamente utile senza dipendenze non CORE.