遍历Perl哈希键的最安全方法是什么?
如果我有一个带(键,值)对的Perl哈希,那么遍历所有键的首选方法是什么?我听说使用each
在某种程度上可能会产生意想不到的副作用。那么,这是真的吗?以下两种方法之一是最佳方法吗?还是有更好的方法?
# Method 1 while (my ($key, $value) = each(%hash)) { # Something } # Method 2 foreach my $key (keys(%hash)) { # Something }
解决方案
回答
我可能会对此一口咬,但我认为这是个人喜好。我找不到文档中对each()的引用不同于keys()或者values()的引用(除了显而易见的"它们返回不同的事物"答案。事实上,文档中指出使用相同的迭代器,并且它们都返回实际的列表值而不是它们的副本,并且在使用任何调用对其进行迭代时修改哈希值是不好的。
话虽如此,我几乎总是使用keys(),因为对我而言,通常是更多自我记录,可通过散列本身访问键的值。当值是对大型结构的引用且哈希键已存储在结构中时,有时会使用values(),此时该键是冗余的,不需要。我想我在Perl编程的10年中两次使用了each()2次,两次可能都是错误的选择=)
回答
我也总是使用方法2. 使用每种方法的唯一好处是,如果我们只是读取(而不是重新分配)哈希条目的值,那么我们就不会经常取消引用哈希。
回答
经验法则是使用最适合我们需要的功能。
如果我们只想要键,并且不打算读取任何值,请使用keys():
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
回答
我通常使用keys
,但我想不起来上一次使用或者阅读过each
的用法。
不要忘记map
,这取决于我们在循环中正在做什么!
map { print "$_ => $hash{$_}\n" } keys %hash;
回答
使用每种语法将阻止立即生成整个键集。如果我们要对具有数百万行的数据库使用绑定哈希,这可能很重要。我们不想一次全部生成整个键列表并耗尽物理内存。在这种情况下,每个都充当迭代器,而键实际上在循环开始之前就生成了整个数组。
因此,"每个"唯一实际使用的地方是散列很大(与可用内存相比)。只有当哈希本身不存在于内存中时,才可能发生这种情况,除非我们正在为手持式数据收集设备或者内存较小的设备编程。
如果内存不是问题,则通常映射或者键范式更为流行,更易于阅读。
回答
关于此主题的一些其他想法:
- 任何哈希迭代器本身都没有不安全的地方。不安全的是在迭代哈希时修改哈希的键。 (修改值是绝对安全的。)我唯一想到的潜在副作用是,"值"会返回别名,这意味着修改它们会修改哈希的内容。这是设计使然,但在某些情况下可能不是我们想要的。
- John接受的答案很好,但有一个例外:文档清楚地表明,在遍历哈希值时添加键并不安全。它可能适用于某些数据集,但不适用于其他数据集,具体取决于哈希顺序。
- 如前所述,删除
each
返回的最后一个键是安全的。对于keys'来说不是这样,因为
each是一个迭代器,而
keys`返回一个列表。
回答
使用" 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"键发生了什么?它们仍然存在,但是
第二个"每个"从第一个停止的地方开始,到哈希结束时停止,所以我们在第二个循环中再也看不到它们。
回答
每个可能导致我们出现问题的地方是它是一个真实的,无作用域的迭代器。举例来说:
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"; }
如果需要确保"每个"都获取所有键和值,则需要确保首先使用"键"或者"值"(因为这会重置迭代器)。请参阅每个文档。
回答
我会说:
- 对大多数人来说,请使用最容易阅读/理解的内容(因此,我通常会说按键)
- 在整个代码库中始终使用决定的内容。
这具有2个主要优点:
- 发现"通用"代码更容易,因此我们可以将其重构为函数/方法。
- 将来的开发人员可以更轻松地进行维护。
我认为在每个密钥上使用键并不昂贵,因此无需为代码中的同一事物使用两个不同的结构。