如果我有一堆(键,值)对的Perl哈希,迭代所有键的首选方法是什么?我听说使用each
可能会以某种方式产生意想不到的副作用。那么,这是真的,并且是以下两种方法中最好的方法之一,还是有更好的方法?
# Method 1
while (my ($key, $value) = each(%hash)) {
# Something
}
# Method 2
foreach my $key (keys(%hash)) {
# Something
}
答案 0 :(得分:188)
经验法则是使用最适合您需求的功能。
如果您只想要密钥并且不打算读取任何值,请使用密钥():
foreach my $key (keys %hash) { ... }
如果您只想要值,请使用values():
foreach my $val (values %hash) { ... }
如果您需要键和值,请使用each():
keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }
如果您计划以任何方式更改散列的键除以在迭代期间删除当前键,则不得使用each()。例如,使用keys()创建一组具有doubled值的新大写键的代码可以正常工作:
%h = (a => 1, b => 2);
foreach my $k (keys %h)
{
$h{uc $k} = $h{$k} * 2;
}
产生预期的结果哈希:
(a => 1, A => 2, b => 2, B => 4)
但是使用each()做同样的事情:
%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)
然而,这是安全的:
keys %h;
while(my($k, $v) = each %h)
{
if(...)
{
delete $h{$k}; # This is safe
}
}
所有这些都在perl文档中描述:
% perldoc -f keys
% perldoc -f each
答案 1 :(得分:24)
使用 each
时应注意的一件事是它有
向您的哈希添加“状态”的副作用(哈希必须记住
什么是“下一个”键)。使用上面发布的代码片段时,
它一次遍历整个哈希,这通常不是一个
问题。但是,你会遇到很难追查问题(我说的是
经验;),将each
与语句一起使用时
last
或return
退出while ... each
循环
处理了所有密钥。
在这种情况下,哈希将记住它已经返回的键,以及
当你下次使用each
时(可能是完全不相关的一部分)
代码),它将继续在这个位置。
示例:
my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );
# find key 'baz'
while ( my ($k, $v) = each %hash ) {
print "found key $k\n";
last if $k eq 'baz'; # found it!
}
# later ...
print "the hash contains:\n";
# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
print "$k => $v\n";
}
打印:
found key bar
found key baz
the hash contains:
quux => 4
foo => 1
键“bar”和baz“发生了什么事?”他们仍然在那里,但是
第二个each
从第一个停止的地方开始,当它到达散列的末尾时停止,所以我们永远不会在第二个循环中看到它们。
答案 2 :(得分:20)
each
可能导致问题的地方在于它是一个真正的非范围迭代器。举例来说:
while ( my ($key,$val) = each %a_hash ) {
print "$key => $val\n";
last if $val; #exits loop when $val is true
}
# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
# continues where the last loop left off
print "$key => $val\n";
}
如果您需要确保each
获取所有键和值,则需要确保首先使用keys
或values
(因为它会重置迭代器)。请参阅documentation for each。
答案 3 :(得分:13)
使用每种语法将阻止一次生成整组密钥。如果您对具有数百万行的数据库使用绑定哈希,这可能很重要。您不希望一次生成整个键列表并耗尽物理内存。在这种情况下,每个都充当迭代器,而键实际上在循环开始之前生成整个数组。
因此,“每个”实际使用的唯一地方是散列非常大(与可用内存相比)。只有在散列本身不存在于内存中时,才会发生这种情况,除非您正在编写手持数据采集设备或内存较小的内容。
如果内存不是问题,通常地图或键范例是更容易阅读和更容易阅读的范例。
答案 4 :(得分:5)
关于这个主题的一些杂项想法:
values
返回别名,这意味着修改它们将修改哈希的内容。这是设计使然,但在某些情况下可能不是您想要的。each
返回的最后一个密钥是安全的。 keys
的不为true,因为each
是迭代器而keys
返回列表。答案 5 :(得分:3)
我可能会被这个人咬伤,但我认为这是个人偏好。我在文档中找不到任何引用,每个()与keys()或values()不同(除了显而易见的“它们返回不同的东西”的答案。实际上,文档声明使用相同的迭代器,它们都是返回实际的列表值而不是它们的副本,并且在使用任何调用迭代它时修改哈希是不好的。
所有这一切,我几乎总是使用keys()因为对我来说,通常更自我记录通过哈希本身访问密钥的值。当值是对大型结构的引用并且哈希的密钥已经存储在结构中时,我偶尔会使用values(),此时密钥是冗余的,我不需要它。我想我在Perl编程的10年中已经使用了每次()2次,这两次都可能是错误的选择=)
答案 6 :(得分:3)
我也总是使用方法2。使用每个的唯一好处是,如果您只是读取(而不是重新分配)散列条目的值,则不会经常取消引用散列。
答案 7 :(得分:2)
我通常使用keys
而我无法想到我上次使用或阅读each
的使用情况。
不要忘记map
,这取决于你在循环中做了什么!
map { print "$_ => $hash{$_}\n" } keys %hash;
答案 8 :(得分:-1)
我说:
这给出了两大优势:
我认为在每个密钥上使用密钥并不昂贵,因此在代码中不需要两个不同的构造用于同一个东西。