首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >每个对象的意外行为

每个对象的意外行为
EN

Stack Overflow用户
提问于 2011-07-12 17:47:08
回答 5查看 115关注 0票数 2
代码语言:javascript
复制
%h = (a => 1, b => 2);

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

输出为:

代码语言:javascript
复制
(a => 1, A => 2, b => 2, B => 8)

而不是

代码语言:javascript
复制
(a => 1, A => 2, b => 2, B => 4)

为什么?

EN

回答 5

Stack Overflow用户

发布于 2011-07-12 18:05:47

来自perldoc -f each

如果在迭代时添加或删除散列的元素,条目可能会被跳过或复制--所以不要这么做。异常:删除each()最近返回的项始终是安全的。

票数 7
EN

Stack Overflow用户

发布于 2011-07-12 17:52:25

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

代码语言:javascript
复制
for my $k (keys %h) {
    $h{uc $k} = $h{$k};
    delete $h{$k};
}

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

代码语言:javascript
复制
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级别)。出于我们的目的,我们可以使用一个非常简单的模型:

代码语言:javascript
复制
#!/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"被添加到哈希表中时,它被放在其他键之前,所以它不会被第二次处理,但是键"B" 被放在其他键之后,所以它在第一次传递时被my_each函数看到(作为键"a"后面的项)。

票数 1
EN

Stack Overflow用户

发布于 2011-07-12 17:56:25

循环正在动态地更改%h,因此它解释b的值的两倍(首先是b,然后是B)。each的语义工作方式是从散列中删除一个对,然后返回它,但是您随后在循环中添加了它,因此它可能会在以后得到处理。您应该首先获取键,然后循环以获取值。例如:

代码语言:javascript
复制
my @keys = keys %h;
foreach (@keys)
{
 $h{uc $_} = $h{$_} * 2;
 delete $h{$_};
}

就像查斯。上面提到的Owens,当each移除元素时,你也必须移除它们。

你可以做的另一件有趣的事情是使用map创建一个新的散列:

代码语言:javascript
复制
my %result  = map {uc $_ => $h{$_} * 2} (keys %h);

然后使用散列%result

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6662261

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档