每个人的意外行为

时间:2011-07-12 09:47:08

标签: perl

%h = (a => 1, b => 2);

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

输出结果为:

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

而不是

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

为什么?

5 个答案:

答案 0 :(得分:7)

来自perldoc -f each

  

如果在迭代时添加或删除哈希的元素,   条目可能会被跳过或重复 - 所以不要这样做。例外:它   删除each()最近返回的项目总是安全的。

答案 1 :(得分:1)

因为each不允许您像for循环那样修改项目。 each只返回哈希的下一个键和值。当您说$h{uc $k} = $h{$k} * 2;时,您正在哈希中创建新值。为了得到你想要的行为,我可能会说

for my $k (keys %h) {
    $h{uc $k} = $h{$k};
    delete $h{$k};
}

如果散列很大并且您担心将所有密钥存储在内存中(这是each的主要用途),那么您最好说:

my %new_hash;
while (my ($k, $v) = each %h) {
    $new_hash{uc $k} = $v;
    delete $h{$k};
}

然后使用%new_hash代替%h

至于为什么某些键会被多次处理,而其他键不能处理,首先我们必须查看the documentation for each

  

如果在迭代时添加或删除哈希的元素,则可能会跳过或复制条目 - 所以不要这样做。

没关系,它告诉我们期待什么,但不是为什么。要了解为什么我们必须创建一个正在发生的事情的模型。为哈希值分配值时,密钥将变为hash function的数字。然后使用此数字索引到一个数组(在C级别,而不是Perl级别)。出于我们的目的,我们可以使用一个非常简单的模型:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my %hash_function = (
        a => 2,
        b => 1,
        A => 0,
        B => 3
);

my @hash_table;

{
    my $position = 0;
    sub my_each {
        #return nothing if there is nothing
        return unless @hash_table;

        #get the key and value from the next positon in the
        #hash table, skipping empty positions
        until (defined $hash_table[$position]) {
            $position++;
            #return nothing if there is nothing left in the array
            return if $position > $#hash_table;
        }
        my ($k, $v) = %{$hash_table[$position]};

        #set up for the next call
        $position++;

        #if in list context, return both key an value
        #if in scalar context, return the key
        return wantarray ? ($k, $v) : $k;
    }
}


$hash_table[$hash_function{a}] = { a => 1 }; # $h{a} = 1;
$hash_table[$hash_function{b}] = { b => 2 }; # $h{b} = 2;

while (my ($k, $v) = my_each) {
    # $h{$k} = $v * 2;
    $hash_table[$hash_function{uc $k}] = { uc $k => $v * 2 };
}

print Dumper \@hash_table;

对于这个例子,我们可以看到当键"A"被添加到哈希表时,它被放在其他键之前,因此它不会被第二次处理,而是键{{ 1}} 确实放在其他键之后,因此"B"函数在第一次传递时看到它(作为键my_each后面的项)。

答案 2 :(得分:1)

循环正在动态更改%h,因此它会将b(第一个b,然后是B)的值解释两倍。 each的语义通过从哈希中删除一对,然后返回它来工作,但之后在循环中添加它,因此可能会在以后处理。您应该首先获取密钥,然后循环以获取值。例如:

my @keys = keys %h;
foreach (@keys)
{
 $h{uc $_} = $h{$_} * 2;
 delete $h{$_};
}

作为查斯。 Owens上面指出,当each删除元素时,你也必须删除它们。

你可以做的另一个可爱的事情是使用map创建一个新的哈希:

my %result  = map {uc $_ => $h{$_} * 2} (keys %h);

然后使用哈希%result

答案 3 :(得分:0)

这对我有用

%h = (a => 1, b => 2);
keys %h;
for my $k (keys %h ) {
    $h{uc $k} = $h{$k} * 2;
}
while ( ($k,$v) = each %h ) {
    print "$k => $v\n";
}

输出:

A => 2
a => 1
b => 2
B => 4

答案 4 :(得分:0)

在你的循环中添加warn $k;可能会让事情变得更清晰 - 我得到的结果与你的结果相同,这是因为它最终使用的键是'a','b'然后'B',所以:

#round 1 ($k='a'):
$h{uc 'a'} = 1 * 2;
# $h{A} = 2;

#round 2: ($k='b'):
$h{uc 'b'} = 2 * 2;
# $h{B} = 4;

#round 3: ($k='B'):
$h{uc 'B'} = 4 * 2;
# $h{B} = 8;

为什么它使用键'B'而不是'A'运行循环?这是因为每次进行循环时都会运行each调用(因此它正在使用新版本的哈希),但是它正在记住它正在使用的最后一个值,因此在这种情况下,当'A'被添加到哈希时,它被分配在'a'之前的位置,所以它永远不会被看到。