recorrer - ¿Cuál es la diferencia entre iterar sobre un archivo con foreach o mientras está en Perl?
recorrer un arreglo en perl (8)
Tengo un FILE
Filehandle en Perl, y quiero iterar sobre todas las líneas en el archivo. ¿Hay alguna diferencia entre los siguientes?
while (<FILE>) {
# do something
}
y
foreach (<FILE>) {
# do something
}
Además de las respuestas anteriores, otro beneficio de usar while
es que puedes usar $.
variable. Este es el número de línea actual del último perldoc perlvar
accedió (ver perldoc perlvar
).
while ( my $line = <FILE> ) {
if ( $line =~ /some_target/ ) {
print "Found some_target at line $./n";
}
}
Agregué un ejemplo sobre esto a la próxima edición de Effective Perl Programming .
Con un while
, puede detener el procesamiento de FILE
y obtener las líneas no procesadas:
while( <FILE> ) { # scalar context
last if ...;
}
my $line = <FILE>; # still lines left
Si usa un foreach
, consume todas las líneas del foreach
incluso si deja de procesarlas:
foreach( <FILE> ) { # list context
last if ...;
}
my $line = <FILE>; # no lines left!
Aquí hay un ejemplo donde foreach
no funcionará, pero while
hará el trabajo
while (<FILE>) {
$line1 = $_;
if ($line1 =~ /SOMETHING/) {
$line2 = <FILE>;
if (line2 =~ /SOMETHING ELSE/) {
print "I found SOMETHING and SOMETHING ELSE in consecutive lines/n";
exit();
}
}
}
Simplemente no puede hacer esto con foreach
porque leerá todo el archivo en una lista antes de ingresar al ciclo y no podrá leer la siguiente línea dentro del ciclo. Estoy seguro de que habrá soluciones para este problema, incluso en Foreach (leer en una matriz viene a la mente), pero definitivamente ofrece una solución muy directa.
Un segundo ejemplo es cuando tiene que analizar un archivo grande (digamos 3GB) en su máquina con solo 2GB de RAM. foreach
simplemente se quedará sin memoria y se bloqueará. Aprendí esto de la manera difícil muy temprano en mi vida de programación perl.
En contexto escalar (es decir, while
) <FILE>
devuelve cada línea por turno.
En contexto de lista (es decir, foreach
) <FILE>
devuelve una lista que consta de cada línea del archivo.
Deberías usar el constructo while
.
Ver perlop - Operadores de E / S para más.
Editar: j_random_hacker correctamente dice que
while (<FILE>) { … }
pisotea
$_
mientras foreach no (foreach localiza$_
primero). ¡Seguramente esta es la diferencia de comportamiento más importante!
Para la mayoría de los propósitos, probablemente no notarás la diferencia. Sin embargo, foreach
lee cada línea en una lista ( no en una matriz ) antes de recorrerla línea por línea, mientras while
lee una línea a la vez. Dado que foreach
utilizará más memoria y requerirá tiempo de procesamiento por adelantado, generalmente se recomienda usar while
para recorrer líneas de un archivo.
EDIT (vía Schwern): El ciclo foreach
es equivalente a esto:
my @lines = <$fh>;
for my $line (@lines) {
...
}
Es lamentable que Perl no optimice este caso especial como lo hace con el operador de rango ( 1..10
).
Por ejemplo, si leo / usr / share / dict / words con un ciclo for
y un ciclo while y hago que duerman cuando terminen, puedo usar ps
para ver cuánta memoria está consumiendo el proceso. Como control, he incluido un programa que abre el archivo pero no hace nada con él.
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
schwern 73019 0.0 1.6 625552 33688 s000 S 2:47PM 0:00.24 perl -wle open my $fh, shift; for(<$fh>) { 1 } print "Done"; sleep 999 /usr/share/dict/words
schwern 73018 0.0 0.1 601096 1236 s000 S 2:46PM 0:00.09 perl -wle open my $fh, shift; while(<$fh>) { 1 } print "Done"; sleep 999 /usr/share/dict/words
schwern 73081 0.0 0.1 601096 1168 s000 S 2:55PM 0:00.00 perl -wle open my $fh, shift; print "Done"; sleep 999 /usr/share/dict/words
El programa for
consume casi 32 megas de memoria real (la columna RSS
) para almacenar el contenido de mis 2,4 meg / usr / share / dict / words. El ciclo while solo almacena una línea a la vez consumiendo solo 70k para el almacenamiento en línea.
el bucle foreach es más rápido que while (que está basado en condiciones).
Actualización: j al azar hacker señala en un comentario que Perl especializa la prueba de falsedad en un ciclo while cuando lee desde un manejador de archivo. Acabo de verificar que leer un valor falso no terminará el ciclo, al menos en perls modernos. Perdón por dirigirlos mal. Después de 15 años de escribir Perl, sigo siendo un novato. ;)
Todos los de arriba están en lo cierto: usa el ciclo while porque será más eficiente con la memoria y te dará más control.
Sin embargo, una cosa graciosa sobre ese ciclo while es que sale cuando la lectura es falsa. Por lo general, eso será al final del archivo, pero ¿y si devuelve una cadena vacía o un 0? Oops! Su programa acaba de salir demasiado pronto. Esto puede suceder en cualquier manejador de archivo si la última línea del archivo no tiene una nueva línea. También puede ocurrir con objetos de archivos personalizados que tienen un método de lectura que no trata las líneas nuevas de la misma manera que los objetos de archivos Perl normales.
He aquí cómo solucionarlo. Compruebe si hay una lectura de valor indefinido que indique el final del archivo:
while (defined(my $line = <FILE>)) {
print $line;
}
El ciclo foreach
no tiene este problema por cierto y es correcto aunque ineficiente.
j_random_hacker mencionó esto en los comentarios a esta respuesta , pero en realidad no lo puso en una respuesta propia, a pesar de que es otra diferencia que vale la pena mencionar.
La diferencia es que while (<FILE>) {}
sobrescribe $_
, mientras que foreach(<FILE>) {}
localiza. Es decir:
$_ = 100;
while (<FILE>) {
# $_ gets each line in turn
# do something with the file
}
print $_; # yes I know that $_ is unneeded here, but
# I''m trying to write clear code for the example
Imprimirá la última línea de <FILE>
.
Sin embargo,
$_ = 100;
foreach(<FILE>) {
# $_ gets each line in turn
# do something with the file
}
print $_;
Se imprimirá 100
. Para obtener lo mismo con un constructo while(<FILE>) {}
, necesitarías hacer:
$_ = 100;
{
local $_;
while (<FILE>) {
# $_ gets each line in turn
# do something with the file
}
}
print $_; # yes I know that $_ is unneeded here, but
# I''m trying to write clear code for the example
Ahora esto imprimirá 100
.