2012-06-06 11 views
15

Używam each iterację mieszania Perl:Czy mogę skopiować hasz bez resetowania "każdego" iteratora?

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

Wtedy dzieje się coś ciekawego i chcę wydrukować hash. Początkowo uważam coś takiego:

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

Ale to nie będzie działać, bo każdy wie, że dzwoni keys (lub values) na hash resetuje iterator wewnętrzne stosowane do each i możemy uzyskać nieskończoną pętlę. Na przykład te skrypty będą działać wiecznie:

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

Nie ma problemu, pomyślałem. Mogłem sporządzić kopię hasza i wydrukować kopię.

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

Ale to też nie działa. Spowoduje to również zresetowanie iteratora each. W rzeczywistości dowolne użycie %hash w kontekście listy wydaje się resetować jego iterator each. Tak więc te działają na zawsze:

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

Czy jest to dokumentowane w dowolnym miejscu? Sensowne jest, że Perl może potrzebować użyć tego samego wewnętrznego iteratora, aby wypchnąć zawartość hasha na stos powrotny, ale mogę też wyobrazić sobie implementacje hash, które nie muszą tego robić.

Co ważniejsze, czy istnieje sposób, aby zrobić to, co chcę? Aby uzyskać dostęp do wszystkich elementów skrótu bez resetowania iteratora each?


Sugeruje to również nie można debugować mieszania wewnątrz each iteracji, albo. Rozważyć uruchomienie debuggera na:

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

Tylko poprzez kontrolę hash gdzie zatrzymuje debugger (powiedzmy, wpisując p %a lub x %a), będzie zmienić wyjście z programu.


Aktualizacja: upload Hash::SafeKeys jako ogólne rozwiązanie tego problemu. Dzięki @gpojd za skierowanie mnie we właściwym kierunku i @cjm za sugestię, która znacznie uprościła rozwiązanie.

+0

Wystąpił ostatnio ten sam problem. Okazało się, że Hash :: SafeKeys jest dość powolny i zależy od rozmiaru skrótu, a Hash :: StoredIterator + domyślne klawisze działają znacznie lepiej. –

+0

@AlexandrEvstigneev - dziękuję, to jest bardzo interesujące. Wprowadzę pewne aktualizacje 'Hash :: SafeKeys' – mob

Odpowiedz

9

Czy próbowałeś/aś skopiować Storable'sdclone?Prawdopodobnie byłoby coś takiego:

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

' perl -MStorable = dclone -e '% a = (foo => 1), podczas gdy (każdy% a) {dclone \% a}' 'nie jest nieskończony pętla, kiedy próbuję. – cjm

+0

To wydaje się działać. Co robi Stages :: dclone? XS magia? –

+3

Sprytnie! Funkcja ['store_hash' w' Stored'] (http://cpansearch.perl.org/src/AMS/Storable-2.30/Storable.xs) zapisuje stan iteratora, iteruje poprzez hash, gdy go przechowuje, a następnie przywraca stan iteratora po zakończeniu. – mob

2

Jak duży jest ten skrót? Ile czasu zajmuje jej iteracja, tak, że zależy Ci na czasie dostępu?

Wystarczy ustawić flagę i zrobić akcję po zakończeniu iteracji:

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

Chociaż nie ma żadnego powodu, aby nie używać each w kodzie wydruku, też, jeśli nie planują na sortowanie przez klucz lub coś.

1

Nie zapominajmy, że keys %hash jest już zdefiniowana po wejściu do pętli while. Jeden mógł po prostu zapisane klucze do tablicy do późniejszego użytku:

my @keys = keys %hash; 

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

    if (something_interesting_happens()) { 

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

Wada:

  • To mniej elegancki (subiektywna)
  • To nie będzie działać, jeśli %hash jest modyfikowany (ale potem Dlatego należałoby użyć each w pierwszej kolejności)

Upside:

  • Używa mniej pamięci poprzez unikanie hash-kopiowanie
1

Niezupełnie. each jest niesamowicie delikatny. Przechowuje stan iteracji na samym iterowanym haszu, stanie, który jest ponownie wykorzystywany przez inne części perla, gdy tego potrzebują. O wiele bezpieczniej jest zapomnieć, że istnieje, i zawsze iterować własną listę z wyniku keys %hash zamiast tego, ponieważ stan iteracji na liście jest przechowywany leksykalnie jako część samej pętli for, więc jest odporny na korozję przez inne rzeczy.

Powiązane problemy