I have a hash, and I want a loop that processes every element. What's the best way to do it?
Being experienced, this may prompt one to ask some further questions, such as "Do the elements need to be processed in key order?" and "Is there anything special about the hash, such as is it a tied hash?" - at which point the novice's eyes glaze over at the mention of tied hashes.
The most efficient way to do what is asked is as follows: (benchmarking is left as an exercise for the reader :)
The reason why this is efficient, is that we are using the 'each' function as an iterator. This means that, rather than having to collate a list of all hash members, then process each one, instead, successive calls to 'each' return another member, until the hash is exhausted.while (my ($key, $value) = each %my_hash) { . . # some processing on the element . }
Where can this go wrong?
It is all very well if the code inside the loop is simple. Consider what happens when you reference the hash (either the whole hash or a member of the hash) inside the loop. This has the effect of resetting the placeholder used by 'each'. Disaster: the loop loops forever over the same element!
This can happen several subroutine calls deep, and can be difficult to track down. In my case, I gave up tracking the bug through all the nested calls, and instead rewrote the loop as follows:
This has fixed my bug, and the code works as desired. However, it occurs to me that there is a circumstance when this approach will also fail. This is when the code is insering and/or deleting members from the hash.for my $key (keys %my_hash) { my $value = $my_hash{$key} # and no need to change the code belo +w . . # some processing on the element . }
Here is a completely safe way to have a loop that involves deletions and/or insertions, without skipping members or losing track of where you are. This code iterates over the original set of hash keys:
Since the set of keys is now held in the array @temp, it is now safe to iterate over these values despite the changes in the actual hash keys.for my $key (my @temp = keys %my_hash) { my $value = $my_hash{$key} # and no need to change the code belo +w . . # some processing on the element . }
Conclusion:
If you know everything you are doing inside the loop will not interfere with the hash, the 'each' approach is best. Otherwise, if you are not changing keys, use the middle approach. Finally save the keys into a temporary array if you know they could be changing.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Iterating hashes safely and efficiently
by Abigail-II (Bishop) on Aug 11, 2003 at 15:08 UTC | |
|
Re: Iterating hashes safely and efficiently
by sauoq (Abbot) on Aug 12, 2003 at 00:04 UTC | |
|
Re: Iterating hashes safely and efficiently
by jarich (Curate) on Aug 12, 2003 at 08:06 UTC | |
|
Re: Iterating hashes safely and efficiently
by adrianh (Chancellor) on Aug 12, 2003 at 11:27 UTC | |
|
Re: Iterating hashes safely and efficiently
by dragonchild (Archbishop) on Aug 11, 2003 at 14:21 UTC | |
by rinceWind (Monsignor) on Aug 11, 2003 at 14:27 UTC |