Qual è la differenza tra iterare su un file con foreach o while in Perl?

Ho un filehandle FILE in Perl, e voglio scorrere tutte le righe nel file. C’è una differenza tra quanto segue?

 while () { # do something } 

e

 foreach () { # do something } 

Per la maggior parte degli scopi, probabilmente non noterai alcuna differenza. Tuttavia, foreach legge ogni riga in una lista ( non una matrice ) prima di attraversarla riga per riga, mentre while legge una riga alla volta. Poiché foreach utilizzerà una quantità maggiore di memoria e richiederà tempi di elaborazione anticipati, in genere è consigliabile utilizzarlo per scorrere le righe di un file.

EDIT (via Schwern): il ciclo foreach è equivalente a questo:

 my @lines = <$fh>; for my $line (@lines) { ... } 

È spiacevole che Perl non ottimizzi questo caso speciale come fa con l’operatore di intervallo ( 1..10 ).

Per esempio, se leggo / usr / share / dict / words con un ciclo for e un ciclo while e li faccio dormire quando sono terminati, posso usare ps per vedere quanta memoria sta consumando il processo. Come controllo ho incluso un programma che apre il file ma non fa nulla con esso.

 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 

Il programma for sta consumando quasi 32 mega di memoria reale (la colonna RSS ) per memorizzare il contenuto delle mie parole 2.4 mega / usr / share / dict /. Il ciclo while memorizza solo una riga per volta che consuma solo 70k per il buffering di riga.

Nel contesto scalare (cioè while ) restituisce ogni riga a turno.

Nel contesto dell’elenco (es. foreach ) restituisce una lista composta da ogni riga del file.

Dovresti usare il costrutto while .

Vedi perlop – I / O Operators per ulteriori informazioni.

Modifica: j_random_hacker dice giustamente questo

 while () { … } 

calpesta $_ mentre foreach non lo fa (foreach localizza $_ prima). Sicuramente questa è la differenza comportamentale più importante!

Oltre alle risposte precedenti, un altro vantaggio dell’uso è che puoi usare $. variabile. Questo è il numero di riga corrente dell’ultimo filehandle a cui si accede (vedi perldoc perlvar ).

 while ( my $line =  ) { if ( $line =~ /some_target/ ) { print "Found some_target at line $.\n"; } } 

Ho aggiunto un esempio che tratta di questo alla prossima edizione di Efficace Programmazione Perl .

Dopo un while , puoi interrompere l’elaborazione di FILE e ottenere comunque le righe non elaborate:

  while(  ) { # scalar context last if ...; } my $line = ; # still lines left 

Se si utilizza un foreach , si consumano tutte le righe nel foreach anche se si interrompe l’elaborazione:

  foreach(  ) { # list context last if ...; } my $line = ; # no lines left! 

Aggiornamento: j hacker casuali evidenzia in un commento che Perl casi speciali il test di falsità in un ciclo while durante la lettura da un handle di file. Ho appena verificato che la lettura di un valore falso non risolverà il ciclo, almeno sui perls moderni. Ci scusiamo per aver sbagliato tutto. Dopo 15 anni di scrittura Perl sono ancora un noob. 😉

Tutti sopra hanno ragione: usa il ciclo while perché sarà più efficiente in termini di memoria e ti darà più controllo.

Una cosa divertente di questo ciclo è che esce quando la lettura è falsa. Di solito sarà end-of-file, ma cosa succede se restituisce una stringa vuota o uno 0? Oops! Il tuo programma è appena uscito troppo presto. Questo può accadere su qualsiasi handle di file se l’ultima riga nel file non ha una nuova riga. Può anche accadere con oggetti file personalizzati che hanno un metodo di lettura che non tratti le newline allo stesso modo degli oggetti file Perl regolari.

Ecco come risolverlo. Verifica la lettura di un valore non definito che indica la fine del file:

 while (defined(my $line = )) { print $line; } 

Il ciclo foreach non presenta questo problema ed è corretto anche se inefficiente.

j_random_hacker ha menzionato questo nei commenti a questa risposta , ma in realtà non lo ha messo in una sua risposta, anche se è un’altra differenza che vale la pena menzionare.

La differenza è che while () {} sovrascrive $_ , mentre foreach() {} localizza. Questo è:

 $_ = 100; while () { # $_ 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 

Stamperà l’ultima riga di .

Però,

 $_ = 100; foreach() { # $_ gets each line in turn # do something with the file } print $_; 

Stamperà 100 . Per ottenere lo stesso con un costrutto while() {} che dovresti fare:

 $_ = 100; { local $_; while () { # $_ 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 

Ora questo stamperà 100 .

Ecco un esempio in cui foreach non funzionerà ma while farà il lavoro

 while () { $line1 = $_; if ($line1 =~ /SOMETHING/) { $line2 = ; if (line2 =~ /SOMETHING ELSE/) { print "I found SOMETHING and SOMETHING ELSE in consecutive lines\n"; exit(); } } } 

Semplicemente non puoi farlo con foreach perché leggerà l’intero file in una lista prima di entrare nel ciclo e non sarai in grado di leggere la riga successiva all’interno del ciclo. Sono sicuro che ci saranno soluzioni alternative a questo problema anche in foreach (viene in mente la lettura di un array), ma sicuramente offre una soluzione molto semplice.

Un secondo esempio è quando devi analizzare un file grande (ad esempio 3 GB) sul tuo computer con solo 2 GB di RAM. foreach semplicemente esaurirà la memoria e andrà in crash. L’ho imparato molto bene molto presto nella mia vita di programmazione perl.

il ciclo foreach è più veloce di while (basato su condizionali).