On Apr 19, 2013, at 6:45 AM, Peter Scott wrote: > On Wed, 17 Apr 2013 11:47:49 -0700, Jim Gibson wrote: >> 1. You should not modify a hash while you are iterating through it with >> the each() function. The each() function uses an internal data structure >> that persists from one call of each to the next. That internal data >> structure can be modified if you add or delete elements (as you are >> doing). > > So the documentation has warned. Yet it should probably be updated, since > that behavior has been demonstrated to be safe: https://groups.google.com/ > forum/?hl=en&fromgroups#!topic/comp.lang.perl.misc/53Lfj8IM0JQ and there > is specific code in the internals to make it safe: https:// > groups.google.com/forum/?hl=en&fromgroups=#!topic/comp.lang.perl.moderated/ > _J9aO8pdAVc
Thanks for weighing in on the subject. It looks like deleting elements is OK, but not adding them. In reference one, Mark-Jason Dominus (MJD) claims that this is safe, and no one was able to come up with a counter-example. In fact, even the "perldoc -f each" entry mentions this case: "If you add or delete a hash's elements while iterating over it, entries may be skipped or duplicated--so don't do that. Exception: It is always safe to delete the item most recently returned by 'each()', so the following code works properly: while (($key, $value) = each %hash) { print $key, "\n"; delete $hash{$key}; # This is safe } " Perhaps this should be extended to say that deleting any element, not just the last one returned by each, is safe. However, the posted program was adding elements to the hash while iterating over it, and this can lead to unexpected or erroneous results. Quoting MJD from the first reference: "Of course, you must not add keys, since it's well-known that this can cause duplications and omissions, and easy to demonstrate." Here is a cleaned-up version of the posted program with added print statements to show what is happening: #!/usr/bin/perl use strict; use warnings; my @words = qw( a b a ); my %names; my $step = 0; foreach my $x (@words) { $step++; print "\nStep $step: starting loop with key $x\n"; if ( ! %names ) { print " adding first key $x\n"; $names{$x} = 1; }else{ my $found; print " searching hash for $x\n"; my $substep = 0; while( my ($key, $value) = each %names ) { $substep++; print " $step.$substep: checking against ($key,$value)\n"; if( $x eq $key ) { print " incrementing key $x\n"; $names{$key} += 1; $found = 1; last; }else{ print " adding key $x\n"; $names{$x} = 1; } } } } print "\nHash entries: (", scalar keys %names, ")\n"; while ( my ($key2, $value2) = each %names ) { print " $key2 => $value2\n"; } __END__ Which produces the output: Step 1: starting loop with key a adding first key a Step 2: starting loop with key b searching hash for b 2.1: checking against (a,1) adding key b 2.2: checking against (b,1) incrementing key b Step 3: starting loop with key a searching hash for a 3.1: checking against (a,1) incrementing key a 3.2: checking against (b,2) adding key a Hash entries: (2) a => 1 b => 2 __END__ This program contains several logic errors, including continuing to search the hash for elements after it has found the one in question. This is also not the way to test a hash for the existence of a key entry. Use 'exists' for that, or just increment the value and let the hash autovivify the element. Nevertheless, this type of logic could be used for more complicated hash traversals. Looking at the output, you can see that in step 2, the program is searching the hash for the key 'b'. When the first key found is 'a', the program adds the element pair ('b',1) to the hash and continues to search. The new element with key 'b' is returned by the each() function in step 2.2. Would anyone expect that the newly-added element would be returned by each()? Not the person who wrote the original version of this program, because it resulted in the value for the key 'b' being 2 instead of the expected 1. I think the advice to be careful about modifying a hash while traversing over it is still valid. Changing element values and deleting elements are probably OK, but do not add elements to the hash. -- To unsubscribe, e-mail: beginners-unsubscr...@perl.org For additional commands, e-mail: beginners-h...@perl.org http://learn.perl.org/