什么是遍历Perl散列键最安全的方法?

Translate

如果我有一个带(键,值)对的Perl哈希,那么遍历所有键的首选方法是什么?我听说each可能以某种方式具有意想不到的副作用。那么,这是真的吗?以下两种方法之一是最佳方法吗?还是有更好的方法?

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
This question and all comments follow the "Attribution Required."

所有的回答

Translate

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

如果您只是想要钥匙,而不打算永远任何值,请使用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
来源
Translate

使用时应注意的一件事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从第一个停止的地方开始,到哈希结束时停止,所以我们在第二个循环中再也看不到它们。

来源
Translate

那里的地方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首先(因为这会重置迭代器)。见每个文件.

来源
Constance Lee
Translate

使用每种语法将阻止立即生成整个键集。如果您要对具有数百万行的数据库使用绑定哈希,这可能很重要。您不想一次全部生成整个键列表并耗尽您的物理内存。在这种情况下,每个都充当迭代器,而键实际上在循环开始之前就生成了整个数组。

因此,“每个”唯一实际使用的地方是散列很大(与可用内存相比)。除非您对手持数据收集设备或内存较小的程序进行编程,否则只有在哈希自身不存在于内存中时,才有可能发生这种情况。

如果内存不是问题,则通常映射或键范式更为流行,更易于阅读。

来源
Translate

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

  1. 任何哈希迭代器本身都没有不安全的地方。不安全的是在遍历哈希时修改哈希键。 (修改值是绝对安全的。)我能想到的唯一潜在副作用是values返回别名,这意味着修改它们将修改哈希的内容。这是设计使然,但在某些情况下可能不是您想要的。
  2. 约翰的接受的答案有一个例外,它是有好处的:文档很清楚,在遍历散列时添加键并不安全。它可能适用于某些数据集,但不适用于其他数据集,具体取决于哈希顺序。
  3. 如前所述,可以安全删除由返回的最后一个密钥each。这是适用于keyseach是一个迭代器,而keys返回列表。
来源
Translate

我也总是使用方法2。使用每种方法的唯一好处是,如果您只是读取(而不是重新分配)哈希条目的值,那么您就不会经常取消引用哈希。

来源
Translate

我可能会对此一口咬,但我认为这是个人喜好。我找不到文档中对each()的引用不同于keys()或values()的引用(除了显而易见的“它们返回不同的事物”答案。事实上,文档中指出使用相同的迭代器,并且它们都返回实际的列表值而不是它们的副本,并且在使用任何调用对其进行迭代时修改哈希值是不好的。

话虽如此,我几乎总是使用keys(),因为对我而言,通常是更多自我记录,可通过散列本身访问键的值。当值是对大型结构的引用且哈希键已存储在结构中时,我有时会使用values(),此时该键是多余的,不需要。我想我在Perl编程的10年中两次使用了each()2次,两次可能都是错误的选择=)

来源
Translate

我通常用keys而且我想不起来我上一次使用或阅读使用each.

不要忘了map,具体取决于您在循环中正在做什么!

map { print "$_ => $hash{$_}\n" } keys %hash;
来源
Translate

我会说:

  1. 对大多数人来说,请使用最容易阅读/理解的内容(所以我通常会说按键)
  2. 在整个代码库中始终使用决定的内容。

这具有2个主要优点:

  1. 发现“通用”代码更容易,因此您可以将其重构为函数/方法。
  2. 将来的开发人员可以更轻松地进行维护。

我认为在每个键上使用键并不昂贵,因此无需为代码中的同一事物使用两个不同的结构。

来源
下一个问题:
链接两个Office文档