2012-06-06 14 views
15

Sto usando each per scorrere un hash Perl:Posso copiare un hash senza resettare il suo "ogni" iteratore?

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

Poi succede qualcosa di interessante e voglio stampare il hash. In un primo momento mi considero qualcosa di simile:

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

Ma questo non funzionerà, perché tutti sanno che la chiamata keys (o values) su un hash reimposta l'iteratore interno utilizzato per each, e possiamo ottenere un ciclo infinito. Ad esempio, questi script verranno eseguiti per sempre:

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

Nessun problema, ho pensato. Potrei fare una copia dell'hash e stampare la copia.

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

Ma anche questo non funziona. Ciò ripristina anche l'iteratore each. In effetti, qualsiasi utilizzo di %hash in un contesto di elenco sembra reimpostare il suo iteratore each. Quindi anche questi durano all'infinito:

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}' 

È documentato ovunque? Ha senso che perl potrebbe aver bisogno di usare lo stesso iteratore interno per spingere i contenuti di un hash su uno stack di restituzione, ma posso anche immaginare implementazioni di hash che non hanno bisogno di farlo.

Ancora più importante, c'è un modo per fare ciò che voglio? Per ottenere tutti gli elementi di un hash senza reimpostare l'iteratore each?


Questo suggerisce, inoltre, non è possibile eseguire il debug di un hash all'interno di un each iterazione, neanche. Si consideri che esegue il debugger on:

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

Proprio controllando l'hash in cui il debugger si interrompe (ad esempio, digitando p %a o x %a), si cambierà l'output del programma.


Aggiornamento: ho caricato Hash::SafeKeys come una soluzione generale a questo problema. Grazie a @gpojd per avermi indicato nella giusta direzione e @cjm per un suggerimento che ha reso la soluzione molto più semplice.

+0

Recentemente si è verificato lo stesso problema. Ho scoperto che Hash :: SafeKeys è piuttosto lento e dipende dalle dimensioni dell'hash e le chiavi predefinite di Hash :: StoredIterator + funzionano molto meglio. –

+0

@AlexandrEvstigneev - grazie, questo è molto interessante. Aggiungerò alcuni aggiornamenti a 'Hash :: SafeKeys' – mob

risposta

9

Hai provato Storable'sdclone per copiarlo?Probabilmente sarebbe qualcosa di simile:

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

' perl -MStorable = dclone -e '% a = (foo => 1); while (ogni% a) {dclone \% a}' 'non è infinito loop quando lo provo. – cjm

+0

Sembra funzionare. Quindi cosa è Storable :: dclone? XS magia? –

+3

Intelligente! La funzione ['store_hash' in' Storable'] (http://cpansearch.perl.org/src/AMS/Storable-2.30/Storable.xs) salva lo stato dell'iteratore, itera attraverso l'hash mentre lo memorizza, e poi ripristina lo stato iteratore quando ha finito. – mob

2

Quanto è grande questo hash? Quanto ci vuole per iterarlo, in modo tale che ti interessi per i tempi dell'accesso?

Basta impostare una bandiera e fare l'azione dopo la fine del ciclo:

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" } 
} 

Anche se non c'è alcuna ragione per non utilizzare each nel codice stampa, troppo, a meno che non stavano progettando di ordinamento per tasto o qualcosa.

1

Non dimentichiamo che keys %hash già definito quando si entra nel ciclo while. Si sarebbe potuto semplicemente salvato le chiavi in ​​un array per un uso successivo:

my @keys = keys %hash; 

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

    if (something_interesting_happens()) { 

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

Downside:

  • E 'meno elegante (soggettiva)
  • non funzionerà se %hash viene modificato (ma poi il motivo per cui si potrebbe usare each in primo luogo)

Upside:?

  • Esso utilizza meno memoria, evitando hash-copiatura
1

Non proprio. each è incredibilmente fragile. Memorizza lo stato di iterazione sull'hash iterato stesso, stato che viene riutilizzato da altre parti di perl quando ne hanno bisogno. Molto più sicuro è dimenticare che esiste, e iterare sempre la propria lista dal risultato di keys %hash invece, perché lo stato di iterazione su una lista è memorizzato lessicamente come parte del ciclo for stesso, quindi è immune dalla corruzione da altre cose.

Problemi correlati