in reply to Hashes: Deleting elements while iterating

The answer the OP was seeking has been well-demonstrated already. However, in attempting to come up with examples to show him why this behaves the way it does, I came across some very interesting behavior.

#!/usr/bin/perl -lw my %hash = qw(a b c d d c b a); foreach my $key (%hash) { print $key; delete $hash{$key}; } # output: # c # # a # Segmentation fault (core dumped) foreach my $key (%hash) { print $key; delete $hash{"$key"}; } # same output with seg fault foreach my $key ( @{[%hash]} ) { print $key; delete $hash{$key}; } # output: # c # d # a # b # b # a # d # c

I've tried it in both 5.6.1 and 5.8.0. From my understanding of hashes, this ought to work since according to perldoc -f delete the delete should just return undef if the hash element doesn't exist.

antirice    
The first rule of Perl club is - use Perl
The
ith rule of Perl club is - follow rule i - 1 for i > 1

Replies are listed 'Best First'.
Re: Re: Hashes: Deleting elements while iterating - deleting phantoms?
by wufnik (Friar) on Sep 02, 2003 at 23:49 UTC
    i tried the above too, and like antirice was also puzzled. however, my perl, 5.8.0, i586 linux, does not die.

    i think the oddities manifest are the result of possibly undefined behaviour when dealing with previously 'deleted' elements on treating the hash as a list. here is a brief pseudoexplanation:

    what happens is the following: taking
    1: foreach my $key (%hash) { 2: print $key; 3: delete $hash{$key}; 4: }
    as an example:

    the first iteration takes a real key, and deletes (line 3) it as if deleting from a hash. the effect of this is to nullify the value associated with the real key.

    when we go to the next iteration, which is done over a *list*, because (camel book) 'modification to (foreach) loop values can change the original values', we end up having undef as an iterator value. ie the ghost of the previously deleted key.

    this undef value remains in the foreach iterator's conception of the hash - presumably because it thinks of the hash as a list. so on next iteration, line 2 attempts to print an undef value, and line 3 attempts to delete it, which has no effect on the actual keys still in the hash.

    you can test this by including a  print join ":", keys %hash after line 3.

    next rinse, we delete a 'real' key from the hash again, and then attempt to iterate on what has become an undefined value.

    so we go through a "delete real key from hash, delete undef from hash" cycle, thanks to our iteration which treats the hash as a list.

    i guess what i am a little surprised about is that those undef values still remain in the concept of the list as seen by foreach. i guess this is a byproduct of treating the hash as a list, and forcing a peek at the phantom value.

    i would be interested if anyone could confirm/deny this was indeed happening.but more, if some kind soul would give an explanation of why the third antirice loop works, i could sleep easy. easier...

    wufnik

    -- in the world of the mules there are no rules --

      if some kind soul would give an explanation of why the third antirice loop works, i could sleep easy. easier...

      foreach my $key ( @{[%hash]} ) { print $key; delete $hash{$key}; }

      What antirice has done here is to take a copy of the hash as a list. After this, interating over the copy means that every second delete attempts to delete a key that never existed in the hash and fails quietly while all the others succeed.

      No doubt you're confused by the

      @{[%hash]}
      bit.

      Enclosing something in square brackets in Perl takes it in list context. It also generates an array reference to that. So %hash here is copied into an annoymous array. This is then deferenced by the @{}s because foreach expects a list, not a reference.

      Does this help?

      All the best,

      jarich

      My perl (AS 5.61 on Windows XP) also dies, but not every time. Using diagnostics, I get the following message, among others:

      Attempt to free unreferenced scalar at scratchpad2.pl line 5 (#2): (W internal) Perl went to decrement the reference count of a scalar to see if it would go to 0, and discovered that it had already gone to 0 earlier, and should have been freed, and in fact, probably was freed. This could indicate that SvREFCNT_dec() was called too many times, or that SvREFCNT_inc() was called too few times, or that the SV was mortalized when it shouldn't have been, or that memory has been corrupted.

      which may help some, but I'm afraid is a little over my head.

      Concerning the third antirice loop:

      foreach my $key ( @{[%hash]} ) { # print "$key: @{[%hash]}\n"; delete $hash{$key}; }
      I thought it looked to be (conceptually) equivalent to something like:

      foreach my $key (my @ary = %hash) { # print "$key: @ary\n"; delete $hash{$key}; }

      (which also 'works', BTW), but apparently it isn't the same, since uncommenting the print lines shows that the anonymous array reference (vocab a bit shaky here...) [%hash] shrinks by two items with each iteration, while the 'straight' array @ary remains unchanged.

      Oh dear, not sure if all that helps at all...

      dave