perl hash iteration each

¿Cuál es la forma más segura de recorrer las claves de un hash Perl?



iteration each (9)

Si tengo un hash de Perl con un montón de pares (clave, valor), ¿cuál es el método preferido para iterar a través de todas las claves? He oído que el uso de each de alguna manera puede tener efectos secundarios no deseados. Entonces, ¿es eso cierto, y es uno de los dos métodos siguientes mejores, o hay una mejor manera?

# Method 1 while (my ($key, $value) = each(%hash)) { # Something } # Method 2 foreach my $key (keys(%hash)) { # Something }


Algunas reflexiones diversas sobre este tema:

  1. No hay nada inseguro en ninguno de los iteradores hash. Lo que no es seguro es modificar las claves de un hash mientras está iterando sobre él. (Es perfectamente seguro modificar los valores). El único efecto secundario potencial que se me ocurre es que los values devuelven alias, lo que significa que modificarlos modificará el contenido del hash. Esto es por diseño, pero puede no ser lo que desea en algunas circunstancias.
  2. La respuesta aceptada de John es buena con una excepción: la documentación es clara de que no es seguro agregar claves mientras se itera sobre un hash. Puede funcionar para algunos conjuntos de datos, pero fallará para otros dependiendo del orden hash.
  3. Como ya se señaló, es seguro eliminar la última clave devuelta por each . Esto no es cierto para las keys ya que each una es un iterador mientras que las keys devuelven una lista.

El lugar donde each puede causarle problemas es que es un iterador verdadero y sin alcance. A modo de ejemplo:

while ( my ($key,$val) = each %a_hash ) { print "$key => $val/n"; last if $val; #exits loop when $val is true } # but "each" hasn''t reset!! while ( my ($key,$val) = each %a_hash ) { # continues where the last loop left off print "$key => $val/n"; }

Si necesita asegurarse de que each obtenga todas las claves y valores, debe asegurarse de usar primero keys o values (ya que eso restablece el iterador). Consulte la documentación de cada uno .


El uso de cada sintaxis evitará que se genere todo el conjunto de claves a la vez. Esto puede ser importante si está utilizando un hash vinculado a una base de datos con millones de filas. No desea generar toda la lista de claves a la vez y agotar su memoria física. En este caso, cada uno sirve como un iterador, mientras que las claves en realidad generan toda la matriz antes de que comience el ciclo.

Por lo tanto, el único lugar donde "cada" es de uso real es cuando el hash es muy grande (en comparación con la memoria disponible). Es probable que eso suceda solo cuando el hash en sí no vive en la memoria a menos que esté programando un dispositivo portátil de recolección de datos o algo con poca memoria.

Si la memoria no es un problema, generalmente el paradigma del mapa o las claves es el paradigma más preventivo y más fácil de leer.


La regla general es utilizar la función más adecuada a sus necesidades.

Si solo desea las claves y no planea leer ninguno de los valores, use las claves ():

foreach my $key (keys %hash) { ... }

Si solo desea los valores, use valores ():

foreach my $val (values %hash) { ... }

Si necesita las claves y los valores, use each ():

keys %hash; # reset the internal iterator so a prior each() doesn''t affect the loop while(my($k, $v) = each %hash) { ... }

Si planea cambiar las claves del hash de alguna manera, excepto para eliminar la clave actual durante la iteración, entonces no debe usar cada (). Por ejemplo, este código para crear un nuevo conjunto de claves en mayúsculas con valores duplicados funciona bien con las teclas ():

%h = (a => 1, b => 2); foreach my $k (keys %h) { $h{uc $k} = $h{$k} * 2; }

produciendo el hash resultante esperado:

(a => 1, A => 2, b => 2, B => 4)

Pero usando cada () para hacer lo mismo:

%h = (a => 1, b => 2); keys %h; while(my($k, $v) = each %h) { $h{uc $k} = $h{$k} * 2; # BAD IDEA! }

produce resultados incorrectos en formas difíciles de predecir. Por ejemplo:

(a => 1, A => 2, b => 2, B => 8)

Esto, sin embargo, es seguro:

keys %h; while(my($k, $v) = each %h) { if(...) { delete $h{$k}; # This is safe } }

Todo esto se describe en la documentación de Perl:

% perldoc -f keys % perldoc -f each


Puede que este me muerda, pero creo que es una preferencia personal. No puedo encontrar ninguna referencia en los documentos para que cada () sea diferente de las claves () o los valores () (aparte de la respuesta obvia "devuelven cosas diferentes". De hecho, los documentos indican que usan el mismo iterador y todos devolver valores de lista reales en lugar de copias de ellos, y que modificar el hash mientras itera sobre él usando cualquier llamada es malo.

Dicho todo esto, casi siempre uso las teclas () porque para mí suele ser más autodocumentado acceder al valor de la clave a través del propio hash. Ocasionalmente uso valores () cuando el valor es una referencia a una estructura grande y la clave para el hash ya estaba almacenada en la estructura, en cuyo punto la clave es redundante y no la necesito. Creo que he usado cada () 2 veces en 10 años de programación de Perl y probablemente fue la elección incorrecta las dos veces =)


Siempre uso el método 2 también. El único beneficio de usar cada uno es que si solo está leyendo (en lugar de reasignar) el valor de la entrada hash, no está constantemente desreferenciando el hash.


Una cosa que debe tener en cuenta al usar each es que tiene el efecto secundario de agregar "estado" a su hash (el hash debe recordar cuál es la tecla "siguiente"). Cuando se usa código como los fragmentos publicados anteriormente, que iteran sobre todo el hash de una vez, esto generalmente no es un problema. Sin embargo, se encontrará con problemas difíciles de rastrear (hablo por experiencia;), cuando use each junto con declaraciones como last o return para salir del while ... each ciclo antes de que haya procesado todas las claves.

En este caso, el hash recordará qué claves ya ha devuelto, y cuando use each de ellas la próxima vez (tal vez en un código totalmente no relacionado), continuará en esta posición.

Ejemplo:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 ); # find key ''baz'' while ( my ($k, $v) = each %hash ) { print "found key $k/n"; last if $k eq ''baz''; # found it! } # later ... print "the hash contains:/n"; # iterate over all keys: while ( my ($k, $v) = each %hash ) { print "$k => $v/n"; }

Esto imprime:

found key bar found key baz the hash contains: quux => 4 foo => 1

¿Qué pasó con las teclas "bar" y baz "? Todavía están allí, pero el segundo comienza donde quedó el primero y se detiene cuando llega al final del hash, por lo que nunca los vemos en el segundo bucle.


Usualmente uso keys y no puedo pensar en la última vez que usé o leí un uso de each .

¡No te olvides del map , dependiendo de lo que estés haciendo en el bucle!

map { print "$_ => $hash{$_}/n" } keys %hash;


Yo diría:

  1. Use lo que sea más fácil de leer / comprender para la mayoría de las personas (por lo que las claves, por lo general, diría)
  2. Use lo que decida de manera consistente en toda la base del código.

Esto le da 2 ventajas principales:

  1. Es más fácil detectar el código "común" para que pueda re-factorizar en funciones / métodos.
  2. Es más fácil de mantener para futuros desarrolladores.

No creo que sea más costoso usar claves sobre cada una, por lo que no es necesario tener dos construcciones diferentes para la misma cosa en su código.