2012-06-06 16 views
15

estoy usando each para iterar a través de un hash de Perl:¿Puedo copiar un hash sin reiniciar su iterador "cada"?

while (my ($key,$val) = each %hash) { 
    ... 
} 

Entonces sucede algo muy interesante y quiero imprimir el hash. Al principio me considero algo como:

while (my ($key,$val) = each %hash) { 
    if (something_interesting_happens()) { 
     foreach my $k (keys %hash) { print "$k => $hash{$k}\n" } 
    } 
} 

Pero eso no va a funcionar, porque todo el mundo sabe que llamar keys (o values) en un hash restablece el iterador interno utilizado para each, y podemos conseguir un bucle infinito. Por ejemplo, estos scripts se ejecutarán para siempre:

perl -e '%a=(foo=>1); while(each %a){keys %a}' 
perl -e '%a=(foo=>1); while(each %a){values %a}' 

No hay problema, pensé. Podría hacer una copia del hash e imprimir la copia.

if (something_interesting_happens()) { 
     %hash2 = %hash; 
     foreach my $k (keys %hash2) { print "$k => $hash2{$k}\n" } 
    } 

Pero eso tampoco funciona. Esto también restablece el iterador each. De hecho, cualquier uso de %hash en un contexto de lista parece restablecer su iterador each. Entonces estos también se ejecutan para siempre:

perl -e '%a=(foo=>1); while(each %a){%b = %a}' 
perl -e '%a=(foo=>1); while(each %a){@b = %a}' 
perl -e '%a=(foo=>1); while(each %a){print %a}' 

¿Se ha documentado esto en algún lugar? Tiene sentido que perl necesite usar el mismo iterador interno para enviar los contenidos de un hash a una pila de retorno, pero también puedo imaginar implementaciones de hash que no necesitan hacer eso.

Más importante, ¿hay alguna manera de hacer lo que quiero? Para obtener todos los elementos de un hash sin restablecer el iterador each?


Esto también sugiere que no se puede depurar un hash dentro de una iteración each, tampoco. Consideran que ejecuta el depurador en:

%a = (foo => 123, bar => 456); 
while (($k,$v) = each %a) { 
    $DB::single = 1; 
    $o .= "$k,$v;"; 
} 
print $o; 

Sólo inspeccionando el hash donde se detiene el depurador (por ejemplo, al escribir o p %ax %a), que va a cambiar la salida del programa.


Actualización: he subido Hash::SafeKeys como una solución general a este problema. Gracias @gpojd por apuntarme en la dirección correcta y @cjm por una sugerencia que simplificó mucho la solución.

+0

Encontré el mismo problema recientemente. Descubrí que Hash :: SafeKeys es bastante lento y depende del tamaño del hash, y que las teclas predeterminadas Hash :: StoredIterator + tienen un rendimiento mucho mejor. –

+0

@AlexandrEvstigneev - gracias, esto es muy interesante. Voy a hacer algunas actualizaciones a 'Hash :: SafeKeys' – mob

Respuesta

9

¿Has probado Storable'sdclone para copiarlo?Probablemente sería algo como esto:

use Storable qw(dclone); 
my %hash_copy = %{ dclone(\%hash) }; 
+3

' perl -MStorable = dclone -e '% a = (foo => 1); mientras que (cada% a) {dclone \% a}' 'no es infinito bucle cuando lo intento. – cjm

+0

Eso parece funcionar. Entonces, ¿qué está haciendo Storable :: dclone? Magia XS? –

+3

¡listo! La función ['store_hash' en' Storable'] (http://cpansearch.perl.org/src/AMS/Storable-2.30/Storable.xs) guarda el estado del iterador, itera a través del hash tal como lo almacena, y luego restaura el estado del iterador cuando termina. – mob

2

¿Qué tan grande es este hash? ¿Cuánto tiempo lleva iterar a través de él, de modo que le importa el momento del acceso?

Sólo hay que establecer una bandera y realizar la acción después del final de la iteración:

my $print_it; 
while (my ($key,$val) = each %hash) { 
    $print_it = 1 if something_interesting_happens(); 
    ... 
} 

if ($print_it) { 
    foreach my $k (keys %hash) { print "$k => $hash{$k}\n" } 
} 

Aunque no hay razón para no usar each en el código impreso, también, a menos que estaban planeando en la clasificación por tecla o alguna cosa.

1

No olvidemos que keys %hash ya está definido cuando se introduce el bucle while. Uno podría simplemente han guardado las llaves en una matriz para su uso posterior:

my @keys = keys %hash; 

while (my ($key,$val) = each %hash) { 

    if (something_interesting_happens()) { 

     print "$_ => $hash{$_}\n" for @keys; 
    } 
} 

Desventaja:

  • Es menos elegante (subjetiva)
  • no va a funcionar si se modifica %hash (pero entonces ¿por qué uno each utilizar en el primer lugar)

al revés:?

  • Se utiliza menos memoria, evitando de hash de copia
1

realidad, no. each es increíblemente frágil. Almacena el estado de iteración en el mismo hash iterado, estado que es reutilizado por otras partes de Perl cuando lo necesitan. Mucho más seguro es olvidar que existe, y siempre iterar tu propia lista del resultado de keys %hash, porque el estado de iteración sobre una lista se almacena léxicamente como parte del bucle for, por lo que es inmune a la corrupción por otros motivos.

Cuestiones relacionadas