Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the safest way to iterate through the keys of a Perl hash?

If I have a Perl hash with a bunch of (key, value) pairs, what is the preferred method of iterating through all the keys? I have heard that using each may in some way have unintended side effects. So, is that true, and is one of the two following methods best, or is there a better way?

# Method 1 while (my ($key, $value) = each(%hash)) {     # Something }  # Method 2 foreach my $key (keys(%hash)) {     # Something } 
like image 578
Rudd Zwolinski Avatar asked Aug 06 '08 02:08

Rudd Zwolinski


People also ask

How do I iterate through a hash in Perl?

Loop over Perl hash values Perl allows to Loop over its Hash values. It means the hash is iterative type and one can iterate over its keys and values using 'for' loop and 'while' loop. In Perl, hash data structure is provided by the keys() function similar to the one present in Python programming language.

How do I print a hash key in Perl?

We can done this by using map function. map {print "$_\n"} keys %hash; map function process its statement for every keys in the hash. The map function is there to transform a list.


2 Answers

The rule of thumb is to use the function most suited to your needs.

If you just want the keys and do not plan to ever read any of the values, use keys():

foreach my $key (keys %hash) { ... } 

If you just want the values, use values():

foreach my $val (values %hash) { ... } 

If you need the keys and the values, use each():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop while(my($k, $v) = each %hash) { ... } 

If you plan to change the keys of the hash in any way except for deleting the current key during the iteration, then you must not use each(). For example, this code to create a new set of uppercase keys with doubled values works fine using keys():

%h = (a => 1, b => 2);  foreach my $k (keys %h) {   $h{uc $k} = $h{$k} * 2; } 

producing the expected resulting hash:

(a => 1, A => 2, b => 2, B => 4) 

But using each() to do the same thing:

%h = (a => 1, b => 2);  keys %h; while(my($k, $v) = each %h) {   $h{uc $k} = $h{$k} * 2; # BAD IDEA! } 

produces incorrect results in hard-to-predict ways. For example:

(a => 1, A => 2, b => 2, B => 8) 

This, however, is safe:

keys %h; while(my($k, $v) = each %h) {   if(...)   {     delete $h{$k}; # This is safe   } } 

All of this is described in the perl documentation:

% perldoc -f keys % perldoc -f each 
like image 100
John Siracusa Avatar answered Oct 23 '22 07:10

John Siracusa


One thing you should be aware of when using each is that it has the side effect of adding "state" to your hash (the hash has to remember what the "next" key is). When using code like the snippets posted above, which iterate over the whole hash in one go, this is usually not a problem. However, you will run into hard to track down problems (I speak from experience ;), when using each together with statements like last or return to exit from the while ... each loop before you have processed all keys.

In this case, the hash will remember which keys it has already returned, and when you use each on it the next time (maybe in a totaly unrelated piece of code), it will continue at this position.

Example:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );  # find key 'baz' while ( my ($k, $v) = each %hash ) {     print "found key $k\n";     last if $k eq 'baz'; # found it! }  # later ...  print "the hash contains:\n";  # iterate over all keys: while ( my ($k, $v) = each %hash ) {     print "$k => $v\n"; } 

This prints:

found key bar found key baz the hash contains: quux => 4 foo => 1 

What happened to keys "bar" and baz"? They're still there, but the second each starts where the first one left off, and stops when it reaches the end of the hash, so we never see them in the second loop.

like image 30
8jean Avatar answered Oct 23 '22 07:10

8jean