如果我有一个带有一堆(键,值)对的 Perl 哈希,那么遍历所有键的首选方法是什么?我听说使用each
可能会以某种方式产生意想不到的副作用。那么,这是真的吗,以下两种方法之一是最好的,还是有更好的方法?
# Method 1
while (my ($key, $value) = each(%hash)) {
# Something
}
# Method 2
foreach my $key (keys(%hash)) {
# Something
}
经验法则是使用最适合您需要的功能。
如果您只想要键并且不打算读取任何值,请使用键():
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() 创建一组新的具有双倍值的大写键的代码可以正常工作:
%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
使用时您应该注意的一件事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
从第一个停止的地方开始,并在到达散列末尾时停止,所以我们在第二个循环中永远看不到它们。
可能导致您出现问题的地方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
(因为这会重置迭代器)。请参阅每个.
使用 each 语法将防止一次生成整个密钥集。如果您使用绑定散列到具有数百万行的数据库,这可能很重要。您不想一次生成整个密钥列表并耗尽您的物理内存。在这种情况下,每个都用作迭代器,而键实际上在循环开始之前生成整个数组。
因此,“每个”真正有用的唯一地方是当哈希非常大时(与可用内存相比)。只有当哈希本身并不存在于内存中时,才会发生这种情况,除非您正在对手持数据收集设备或内存小的东西进行编程。
如果内存不是问题,通常地图或键范式是更流行且更易于阅读的范式。
关于这个主题的一些其他想法:
values
返回别名,这意味着修改它们将修改散列的内容。这是设计使然,但在某些情况下可能不是您想要的。each
。这是不正确的,keys
因为each
它是一个迭代器,而keys
返回一个列表。我可能会被这个咬伤,但我认为这是个人喜好。我在文档中找不到任何对 each() 与 keys() 或 values() 不同的引用(除了明显的“它们返回不同的东西”答案。事实上,文档声明使用相同的迭代器,它们都是返回实际的列表值而不是它们的副本,并且在使用任何调用对其进行迭代时修改散列是不好的。
话虽如此,我几乎总是使用 keys() 因为对我来说,通过哈希本身访问密钥的值通常更自我记录。当值是对大型结构的引用并且散列的键已经存储在结构中时,我偶尔会使用 values(),此时键是多余的,我不需要它。我想我在 10 年的 Perl 编程中使用了 each() 2 次,这两次都可能是错误的选择 =)
我也总是使用方法2。使用 each 的唯一好处是,如果您只是读取(而不是重新分配)散列条目的值,则不会经常取消引用散列。
我通常使用keys
,但我想不起我上次使用或阅读使用each
.
不要忘记map
,这取决于您在循环中所做的事情!
map { print "$_ => $hash{$_}\n" } keys %hash;
我想说:
这有两个主要优点:
我不认为在每个上使用键更昂贵,因此不需要在代码中为同一事物使用两种不同的构造。