W Perlu hashe (tablice asocjacyjne) są potężnym narzędziem do przechowywania par klucz-wartość. Praca z nimi wymaga jednak uwagi, szczególnie przy jednoczesnym iterowaniu i modyfikowaniu struktury. Brakujące szczegóły mogą prowadzić do błędów, które trudno zdiagnozować.
Tablice asocjacyjne zostały wprowadzone w wczesnych wersjach Perla (Perl 1/2), co uczyniło je jednym z pierwszych języków z pełnym wsparciem dla hashy na poziomie jądra. Z czasem pojawiły się dodatkowe możliwości: iteracja przez each, usuwanie (delete), masowe przekształcenia (map, grep) oraz walka z modyfikowaniem rozmiaru/zawartości podczas przechodzenia.
Iterowanie przez hash i jednoczesna modyfikacja jego zawartości, szczególnie usuwanie elementów, może prowadzić do nieoczekiwanych efektów: pomijania elementów, ponownego przechodzenia po tych samych kluczach, a nawet do nieskończonej pętli. Dodatkowo, kolejność przechodzenia po kluczach nie jest gwarantowana i może się zmieniać między wersjami Perla.
each, ponieważ wewnętrzny kursor może się zepsućkeys, a następnie przechodź przez nią w osobnej pętli i usuwajwhile (my ($k, $v) = each %h) do zwykłej iteracji, ale nie łącz tego z delete wewnątrz pętli, jeśli nie chcesz niespodzianekPrzykład prawidłowego usuwania elementów:
my %h = (a=>1, b=>2, c=>3); for my $k (keys %h) { delete $h{$k} if $h{$k} == 2; }
Przykład niewłaściwego podejścia:
while (my ($k, $v) = each %h) { delete $h{$k}; # To może prowadzić do pomijania kluczy }
Kluczowe cechy:
Czy można bezpiecznie usuwać elementy z hasha wewnątrz pętli while (each %h)?
Nie, to może prowadzić do pomijania części hasha z powodu zerwania wewnętrznego kursora iteracji.
Co się dzieje z kolejnością kluczy po usunięciu elementów z hasha?
Kolejność nie jest gwarantowana i może się zmienić. Poza tym, kolejność iteracji między programami w tym samym Perlu może się różnić.
Czy można zmienić wartość elementu hasha podczas iteracji przez each?
Tak, zmiana wartości (ale nie struktury) jest bezpieczna.
Przykład:
while (my ($k, $v) = each %h) { $h{$k} = $v + 10; }
Użycie usuwania elementów przez each w jednej pętli:
my %h = (a=>1, b=>2, c=>3); while (my ($k, $v) = each %h) { delete $h{$k} if $v == 1; }
Zalety:
Wady:
Tworzenie listy kluczy do usunięcia:
my %h = (a=>1, b=>2, c=>3); for my $k (keys %h) { delete $h{$k} if $h{$k} == 1; }
Zalety:
Wady: