In Perl, hashes (associative arrays) are a powerful tool for storing key-value pairs. However, working with them requires caution, especially when simultaneously iterating and modifying the structure. Missing details can lead to errors that are difficult to diagnose.
Associative arrays were introduced in the early versions of Perl (Perl 1/2), making it one of the first languages with full hash support at the core level. Over time, additional features emerged: iteration with each, deletion (delete), mass transformation (map, grep), and dealing with changes in size/content during traversal.
Iterating over a hash and simultaneously modifying its contents, especially deleting elements, can lead to unexpected effects: skipping elements, re-visiting the same keys, or even an infinite loop. Moreover, the order of key traversal is not guaranteed and can vary between Perl versions.
each, as the internal cursor gets messed up.keys, then iterate over it in a separate loop and delete.while (my ($k, $v) = each %h) for normal iteration, but do not combine it with delete inside the loop if you want to avoid surprises.Example of correct element deletion:
my %h = (a=>1, b=>2, c=>3); for my $k (keys %h) { delete $h{$k} if $h{$k} == 2; }
Example of incorrect approach:
while (my ($k, $v) = each %h) { delete $h{$k}; # This may lead to skipping keys }
Key features:
each is sensitive to structural changes during execution.Is it safe to delete elements from a hash inside a while (each %h) loop?
No, this may lead to skipping parts of the hash due to the reset of the internal iterator cursor.
What happens to the order of keys after deleting elements from the hash?
The order is not guaranteed and may change. Additionally, the order of traversal between programs on the same version of Perl may differ.
Can you change the value of a hash element during iteration through each?
Yes, changing the value (but not the structure) is safe.
Example:
while (my ($k, $v) = each %h) { $h{$k} = $v + 10; }
Using element deletion through each in one loop:
my %h = (a=>1, b=>2, c=>3); while (my ($k, $v) = each %h) { delete $h{$k} if $v == 1; }
Pros:
Cons:
Creating a list of keys for deletion:
my %h = (a=>1, b=>2, c=>3); for my $k (keys %h) { delete $h{$k} if $h{$k} == 1; }
Pros:
Cons: