遍历Perl哈希键的最安全方法是什么?

如果我有一堆(键,值)对的Perl哈希,迭代所有键的首选方法是什么?我听说每个使用可能会以某种方式产生意想不到的副作用。那么,这是否属实,并且是以下两种方法中最好的一种,还是有更好的方法?

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
0
额外 编辑
意见: 4

8 答案

经验法则是使用最适合您需求的功能。

如果您只想使用这些键并且不打算永久读取的任何值,请使用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()可以很好地使用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
0
额外
每个人都有另一个警告。迭代器绑定到散列,而不是上下文,这意味着它不是可重入的。例如,如果循环遍历散列,并且打印散列perl将在内部重置迭代器,从而无限循环代码:my hash =(a => 1,b => 2,c => 3); while(my($ k,$ v)= each%hash){print%hash; }请阅读 blogs.perl.org/用户/ rurban / 2014/04 /拒收的用each.html
额外 作者 Rawler,
请添加一个void-context键%h;在每个循环之前使用迭代器安全地显示。
额外 作者 ysth,

关于此主题的一些其他想法:

  1. 任何哈希迭代器本身都没有不安全的地方。什么是不安全的是修改散列键,而你迭代它。 (修改值是完全安全的。)我能想到的唯一潜在副作用是 values 返回别名,这意味着修改它们将修改哈希的内容。这是设计的,但在某些情况下可能不是您想要的。
  2. John的接受的答案
  3. a>很好,只有一个例外:文档很清楚,在迭代散列时添加键是不安全的。它可能适用于某些数据集,但取决于哈希顺序会失败。
  4. 如前所述,删除每个返回的最后一个键是安全的。对于 keys ,这是 not true,因为每个是一个迭代器,而 keys 返回一个列表。
0
额外
重新“不适用于键”,而是:它不适用于键,任何删除都是安全的。您使用的措辞意味着使用密钥时删除任何内容都是不安全的。
额外 作者 ysth,
Re:“对于任何哈希迭代器都没有任何不安全性”,另一个危险是假设迭代器在开始每个循环之前就开始了,就像其他人提到的那样。
额外 作者 ysth,

我通常使用 keys ,我想不出最后一次使用或读取每个的用法。

不要忘记 map ,这取决于你在循环中做什么!

map { print "$_ => $hash{$_}\n" } keys %hash;
0
额外
除非你想要返回值,否则不要使用map
额外 作者 ko-dos,

在使用 each 时应注意的一点是它具有 添加“状态”到你的散列(哈希必须记住的副作用 什么是“下一个”键)。当使用上面发布的代码片段之类的代码时, 它一次遍历整个散列,这通常不是 问题。但是,你会很难找到问题(我从中发表意见 经验;),当使用每个和类似的语句时 lastreturn 从...之前的 while ... 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

关键“酒吧”和“巴兹”发生了什么事?他们仍然在那里,但是 第二个 each 从第一个停止的地方开始,并在到达散列末尾时停止,因此我们从不在第二个循环中看到它们。

0
额外

我总是使用方法2。使用每种方法的唯一好处是,如果您只是阅读(而不是重新分配)哈希项的值,那么您并不总是取消参考哈希值。

0
额外

我可能会被这一个咬伤,但我认为这是个人偏好。我不能在文档中找到每个()与key()或values()不同的引用(除了显而易见的“他们返回不同的东西”的答案之外。实际上文档声明使用相同的迭代器,并且它们全部返回实际的列表值而不是它们的副本,并且在使用任何调用迭代它时修改哈希值是不好的。

总而言之,我几乎总是使用keys(),因为对我来说,通常通过散列本身访问密钥的值通常需要更多的自我记录。当值是一个大型结构的引用时,偶尔使用values(),并且哈希键已经存储在结构中,此时键是多余的,我不需要它。我认为我已经在10年的Perl编程中使用过每个()两次,并且这两次都可能是错误的选择=)

0
额外

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 获取所有键和值,则需要确保先使用 keysvalues 重置迭代器)。请参阅每个文档

0
额外
如果不小心,这可能会在$ $$
额外 作者 sdkks,

我会说:

  1. 对大多数人来说,使用最简单的方法来阅读/理解(通常我会争论的关键点)
  2. 使用您在整个代码库中始终如一的决定。

这给了2个主要优点:

  1. 发现“通用”代码更容易,因此您可以重新考虑功能/方法。
  2. 未来的开发者更容易维护。

我不认为使用每个键的代价会更加昂贵,所以在代码中不需要针对同一事物使用两种不同的结构。

0
额外
使用 keys 可以增加 hash-size * avg-key-size 的内存使用量。由于密钥大小仅受内存的限制(因为它们只是数组元素,比如“它们的相应值”),在某些情况下,内存使用率和时间都会更加昂贵采取复制。
额外 作者 Adrian Günter,