Perl 每个人的意外行为

Perl 每个人的意外行为,perl,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) 为什么?因为每个都不允许您像for循环那样就地修改项目each只返回哈希的下一个键和值。当您说$h{uc$k}=$h{$k}*2时,您正在散列中创建新值。为了得到你想要的

输出为:

%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)

为什么?

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

(a => 1, A => 2, b => 2, B => 4)
如果散列非常大,并且您担心将所有密钥存储在内存中(这是每个
的主要用途),那么您最好说:

for my $k (keys %h) {
    $h{uc $k} = $h{$k};
    delete $h{$k};
}
然后使用
%new\u hash
而不是
%h

至于为什么有些密钥会被多次处理,而另一些则不会,首先我们必须关注:

如果您在对散列进行迭代时添加或删除散列的元素,则可能会跳过或复制条目——所以不要这样做

这很好,它告诉我们期待什么,但不是为什么。看看为什么我们必须创建一个正在发生的事情的模型。当您为散列赋值时,键会被一个符号转换为一个数字。然后使用这个数字索引到数组中(在C级别,而不是Perl级别)。出于我们的目的,我们可以使用非常简单的模型:

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

对于这个例子,我们可以看到,当键
“A”
被添加到哈希表中时,它被放在其他键之前,因此它不会被第二次处理,但是键
“B”
被放在其他键之后,因此
my\u each
函数在第一次传递时会看到它(作为键
“a”
)后面的项。

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

#!/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;
正如上面Chas.Owens所指出的,当
每个
删除元素时,您也必须删除它们

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

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

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

这对我有用

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

%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";
}

向循环中添加一个
warn$k;
可能会让事情变得更清楚一点-我得到的结果与您相同,这是因为它最终使用的键是“a”、“b”然后是“b”,因此:

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

为什么它使用键“B”而不是“A”运行循环?这是因为每次通过循环时都会运行
每个
调用(因此它使用的是新版本的哈希),但它会记住它使用的最后一个值,因此在本例中,当将“A”添加到哈希中时,它会被分配到“A”之前的一个位置,因此它永远不会被看到。

似乎
B
计算了两次,为什么?因为更改条目可能会改变它们存储的顺序,从而使“each”所包含的数据无效迭代器正在使用。为什么
a
不会被解释两次?在第一种情况下,因为首先计算键。在第二种情况下,因为
map
正在创建一个新的哈希。这是由于所使用的哈希函数的性质。不同版本的Perl具有略微不同的哈希函数和/或种子(Perl 5.8.0实际上保证了每次运行程序时的随机顺序)。Perl保证只要不添加或删除键,键的顺序就会保持不变。这就是为什么
每个
的文档都说“如果在迭代过程中添加或删除散列的元素,则可能会跳过或复制条目,因此不要这样做。”通常,“意外”表示“我没有阅读文档”。如果某些内容无法按预期方式运行,请阅读文档。您不必猜测。:)
#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;