2010-03-02 14 views
16

que tienen Hash donde los valores de las claves son otros valores hash.¿Cómo iterar a través de hash (de Hashes) en Perl?

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

Como puedo iterar a través de esta estructura?

+1

Podría dar un ejemplo más realista. ¿Dónde encuentras esa estructura? ¿Para qué se usa esto? ¿Qué quieres hacer? ¿Quizás otra estructura de datos sería más apropiada para la tarea? – Aurril

+0

@Aurril: las estructuras hash anidadas son útiles para muchas cosas, consulte el enlace en mi publicación a continuación para ver un ejemplo. – Zaid

+0

¿Cada hash individual tiene más de una clave? – Svante

Respuesta

12

¿Esto es lo que quieres? (No probado)

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

Me gusta su condición Yoda en la 4ª línea :) –

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

¡Debería ser $ foo {$ keyname} no% foo {$ keyname}! –

+0

Así debería ser. Eso es lo que recibo por publicar antes del café. – monksp

0

Tendrá que recorrerlo dos veces. es decir,

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

This post puede ser útil.

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

En el OP, los primeros corchetes de la estructura de datos son llaves que indican que es una referencia hash. my $ hash = {'key' => {'key2' => {'key3' => 'value'}}} Por lo tanto, tendrá que eliminar la referencia – ccheneson

+1

Esta solución solo funciona, si hay un número definido fijo de subhashes . Si Hashstructure se autogenera, entonces necesita un enfoque más genérico. Un algoritmo recursivo sería una mejor solución imo. No estoy familiarizado con Perl, de lo contrario daría un ejemplo. – Aurril

+0

@ccheneson: no hay necesidad de desreferencia. Es lo que es. – Zaid

23

Esta respuesta se basa en la idea detrás de Dave Hinton - a saber, para escribir una subrutina de propósito general que caminar una estructura de hash. Este hash walker toma una referencia de código y simplemente llama a ese código para cada nodo de hoja en el hash.

Con este enfoque, el mismo hash andador se puede utilizar para hacer muchas cosas, dependiendo de la devolución de llamada que le damos. Para mayor flexibilidad, lo que tendría que pasar dos devoluciones de llamada - se ha de invocar cuando el valor es una referencia a un hash y el otro a invocar cuando se trata de un valor escalar ordinario. Estrategias como esta se exploran con mayor profundidad en el excelente libro 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

Esto me ayudó mucho, gracias –

7

Las respuestas anteriores muestran cómo rodar su propia solución, que es bueno para hacer al menos una vez para que pueda comprender cómo las tripas de Perl referencias y las estructuras de datos de trabajo. Definitivamente, usted debe tomar una lectura a través perldoc perldsc y perldoc perlref si no lo ha hecho.

Sin embargo, no necesita escribir su propia solución: ya hay un módulo en CPAN que iterará a través de estructuras de datos complejas arbitrariamente para usted: Data::Visitor.

+0

+1 Gracias, 'Data :: Visitor' parece útil. No fue inmediatamente evidente en los documentos cómo hacer algo simple, por ejemplo, atravesar una estructura de hash anidada, imprimir valores de hoja y sus claves (inmediato y sus antepasados). Estoy seguro de que es factible; solo necesito rodear mi cabeza un poco. :) – FMc

1

Esto no es realmente una respuesta nueva, pero quería compartir cómo hacer más que simplemente imprima todos los valores hash recursivamente, pero también para modificarlos si es necesario.

Aquí está mi siempre tan ligera modificación de la respuesta de la dave4420 en el que se pasa el valor de la devolución de llamada como una referencia para mi rutina de devolución de llamada entonces podría modificar todos los valores de la matriz.

También tuve que reconstruir el hash como el tiempo cada bucle crea copias no referencias.

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 está utilizando Perl como un "intérprete CPAN" entonces, además de Data::Visitor y Data::Deep existe la súper 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; 

salida:

t => 10 
y => 11 

$a y $b se tratan como variaciones especiales bles aquí (como con sort()) mientras está dentro de la función traverse(). Data::Traverse es un módulo muy simple pero inmensamente útil sin dependencias que no sean CORE.

Cuestiones relacionadas