Pregunta

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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
¿Fue útil?

Solución

La regla general es utilizar la función que mejor se adapte a sus necesidades.

Si solo quieres las llaves y no piensas hacerlo nunca leer cualquiera de los valores, use las teclas():

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

Si solo quieres los valores, usa valores():

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

Si necesitas las llaves y los valores, use cada():

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

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

%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 each() 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 de maneras difíciles de predecir.Por ejemplo:

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

Sin embargo, esto 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

Otros consejos

Una cosa que debes tener en cuenta al usar each es que tiene el efecto secundario de agregar "estado" a su hash (el hash tiene que recordar cuál es la "próxima" clave).Cuando se usa el 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 de rastrear los problemas (hablo por experiencia;), cuando use each junto con declaraciones comolast o return para salir de la while ... each bucle antes de haber procesado todas las teclas.

En este caso, el hash recordará qué claves ya ha regresado y cuando usa each En él la próxima vez (tal vez en un código de código no relacionado total), 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 ahí, pero el segundo each comienza donde lo dejó el primero y se detiene cuando llega al final del hash, por lo que nunca los vemos en el segundo ciclo.

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 estar seguro de que each obtiene todas las claves y valores, debe asegurarse de usar keys o values primero (ya que eso restablece el iterador).Ver el documentación para cada.

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 querrás generar la lista completa de claves de una vez y agotar tu memoria física.En este caso, cada uno sirve como iterador, mientras que las claves en realidad generan la matriz completa antes de que comience el ciclo.

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

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

Algunas reflexiones diversas sobre este tema:

  1. No hay nada inseguro en ninguno de los iteradores de hash.Lo que no es seguro es modificar las claves de un hash mientras lo iteras.(Es perfectamente seguro modificar los valores). El único efecto secundario potencial que se me ocurre es que values devuelve alias, lo que significa que modificarlos modificará el contenido del hash.Esto es así por diseño, pero puede que no sea lo que desea en algunas circunstancias.
  2. Juan respuesta aceptada es bueno con una excepción:la documentación es clara en cuanto a 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 de hash.
  3. Como ya se señaló, es seguro eliminar la última clave devuelta por each.Esto es no cierto para keys como each es un iterador mientras keys devuelve una lista.

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

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

Dicho todo esto, casi siempre uso claves() porque para mí suele ser más autodocumentado acceder al valor de la clave a través del propio hash.De vez en cuando uso valores() cuando el valor es una referencia a una estructura grande y la clave del hash ya estaba almacenada en la estructura, momento en el cual la clave es redundante y no la necesito.Creo que he usado each() 2 veces en 10 años de programación en Perl y probablemente fue la elección equivocada en ambas ocasiones =)

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

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

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

Yo diría:

  1. Utilice lo que sea más fácil de leer/comprender para la mayoría de las personas (por lo general, diría que las claves)
  2. Utilice lo que decida de forma coherente en todo el código base.

Esto proporciona 2 ventajas principales:

  1. Es más fácil detectar código "común" para poder refactorizarlo en funciones/métodos.
  2. Es más fácil de mantener para los futuros desarrolladores.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top