What happens if we change/update a hash value inside an each loop?

3.6k Views Asked by At

The 'perldoc -f each' say me that is not safe delete or add a value while iterating, except if the item is the most recently returned by each().

When I run this code snnipet:

my ($key,$value);

my %fruits = qw/ banana 1 apple 2 grape 3 /;

while ( $key = each %fruits ) {
    $fruits{$key} *= 7;
    print "$key = $fruits{$key}\n";
}

print "\nRead only\n\n";

while ( ($key,$value) = each %fruits ) {
    print "$key = $value\n";
}

everthing works fine!

But if I use a tied hash, humnn:

#-------------------------------------------------------------------------------
# select entries on database to print or to update.
sub select_urls {
    my ($dbPath,$match,$newValue) = @_;

    tie(my %tiedHash,'DB_File',$dbPath,O_RDWR|O_EXLOCK,0600,$DB_BTREE) || die("$program_name: $dbPath: $!\n");

    while ( my($key,$value) = each %tiedHash ) {
        if ( $key =~ $match ){
            if ( defined $newValue ) {
                $tiedHash{$key} = $newValue;
                ($key,$value) = each %tiedHash; # because 'each' come back 1 step when we update the entry
                print "Value changed --> $key = $value\n";
            } else {
                print "$key = $value\n";
            }
        }
    }

    untie(%tiedHash) ||  die("$program_name: $dbPath: $!\n");
}

was necessary a second call to each().

I have to 'perl -v':

$ perl -v

This is perl 5, version 12, subversion 2 (v5.12.2 (*)) built for amd64-openbsd (with 8 registered patches, see perl -V for more detail)

Copyright 1987-2010, Larry Wall ...

I thinking if it's a bug?!!

Perhaps much more things are behind the scenes...

I asking if my solution is correct???

1

There are 1 best solutions below

9
On

It's the addition or removal of elements (keys) that's the problem. There should be no issue changing the value. There's no inherent difference with tied hashes.

my ($key,$value);

use Tie::Hash;

tie my %fruits, 'Tie::StdHash';
%fruits = qw/ banana 1 apple 2 grape 3 /;

while ( $key = each %fruits ) {
    $fruits{$key} *= 7;
    print "$key = $fruits{$key}\n";
}

print "\nRead only\n\n";

while ( ($key,$value) = each %fruits ) {
    print "$key = $value\n";
}

Output:

banana = 7
apple = 14
grape = 21

Read only

banana = 7
apple = 14
grape = 21

Your second snippet does not demonstrate a bug. It doesn't demonstrate much of anything. It's not runnable, you didn't specify what it outputs, and you didn't specify what you expect it to output. But let's see if there's a problem with DB_File.

use DB_File qw( $DB_BTREE );
use Fcntl   qw( O_RDWR O_CREAT );  # I don't have O_EXLOCK

my ($key,$value);

tie(my %fruits, 'DB_File', '/tmp/fruits', O_RDWR|O_CREAT, 0600, $DB_BTREE)
   or die $!;
%fruits = qw/ banana 1 apple 2 grape 3 /;

while ( $key = each %fruits ) {
    $fruits{$key} *= 7;
    print "$key = $fruits{$key}\n";
}

print "\nRead only\n\n";

while ( ($key,$value) = each %fruits ) {
    print "$key = $value\n";
}

Nope.

apple = 14
banana = 7
grape = 21

Read only

apple = 14
banana = 7
grape = 21