2010-03-02 10 views
16

J'ai Hash où les valeurs de clés sont d'autres Hashes.Comment faire une itération à travers Hash (of Hashes) en Perl?

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

Comment puis-je itérer cette structure?

+1

Pourriez-vous donner un exemple plus réaliste. Où rencontrez-vous une telle structure? A quoi cela sert? Qu'est-ce que tu veux faire? Peut-être qu'une autre structure de données serait plus appropriée pour la tâche? – Aurril

+0

@Aurril: Les structures de hachage imbriquées sont utiles pour beaucoup de choses, voir le lien dans mon post ci-dessous pour un exemple. – Zaid

+0

Est-ce que chaque hash individuel a plus d'une clé? – Svante

Répondre

12

Est-ce ce que vous cherchez? (Non testé)

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

J'aime votre condition Yoda dans la 4ème ligne :) –

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

Cela devrait être $ foo {$ keyname} et non% foo {$ keyname}! –

+0

Donc, il devrait. C'est ce que je reçois pour poster avant le café. – monksp

0

Vous devrez faire une boucle deux fois. c'est-à-dire

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

This post peut être 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

Dans l'OP, les premiers crochets de la structure de données sont des accolades indiquant qu'il s'agit d'une référence de hachage. my $ hash = {'key' => {'key2' => {'key3' => 'value'}}} Donc vous aurez besoin de déréférencer – ccheneson

+1

Cette solution ne fonctionne que s'il y a un nombre fixe défini de sous-ensembles . Si la Hashstructure est générée automatiquement, vous avez besoin d'une approche plus générique. Un algorithme récursif serait une meilleure solution. Je ne connais pas Perl, sinon je donnerais un exemple. – Aurril

+0

@ccheneson: Pas besoin de déréférencer. C'est ce que c'est. – Zaid

23

Cette réponse repose sur l'idée derrière Dave Hinton de - à savoir, d'écrire un sous-programme d'usage général pour marcher une structure de hachage. Un tel hachage prend une référence de code et appelle simplement ce code pour chaque noeud feuille dans le hachage.

Avec une telle approche, le même hash walker peut être utilisé pour faire beaucoup de choses, selon le rappel que nous lui donnons. Pour encore plus de flexibilité, vous devrez passer deux callbacks - un pour invoquer quand la valeur est une référence de hachage et l'autre pour invoquer quand il s'agit d'une valeur scalaire ordinaire. Des stratégies comme celle-ci sont explorées plus en profondeur dans l'excellent livre de 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

Cela m'a beaucoup aidé, merci –

7

Les réponses précédentes montrent comment rouler votre propre solution, ce qui est bon de le faire au moins une fois afin de comprendre le courage de la façon dont les références perl et le travail des structures de données. Vous devriez certainement prendre une lecture à travers perldoc perldsc et perldoc perlref si vous ne l'avez pas déjà fait.

Cependant, vous n'avez pas besoin d'écrire votre propre solution - il existe déjà un module sur CPAN qui va parcourir pour vous les structures de données arbitrairement complexes: Data::Visitor.

+0

+1 Merci, 'Data :: Visitor' semble utile. Les docs ne voyaient pas immédiatement comment faire quelque chose de simple - par exemple, traverser une structure de hachage imbriquée, imprimer des valeurs de feuille et leurs clés (immédiates et leurs ancêtres). Je suis sûr que c'est faisable; juste besoin d'envelopper ma tête autour d'un peu. :) – FMc

1

Ce n'est pas vraiment une nouvelle réponse, mais je voulais partager comment faire plus de il suffit d'imprimer toutes les valeurs de hachage récursivement, mais aussi de les modifier si nécessaire.

Voici ma très légère modification de la réponse du dave4420 dans lequel la valeur est passée à la fonction de rappel comme référence pour mon rappel de routine pourrait alors modifier toutes les valeurs dans le hachage.

J'ai également dû reconstruire le hachage comme le temps tandis que chaque boucle crée des copies pas des références.

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

Si vous utilisez Perl comme un "interprète CPAN", puis, en plus de Data::Visitor et Data::Deep il y a le super simple 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; 

Sortie:

t => 10 
y => 11 

$a et $b sont traités comme des varia spéciaux bles ici (comme avec sort()) tandis que dans la fonction traverse(). Data::Traverse est un module très simple mais extrêmement utile sans dépendance non-CORE.