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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
有帮助吗?

解决方案

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

如果您只想要钥匙并且不打算永远 任何值,使用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

其他提示

使用时应该注意的一件事 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 语法将阻止一次生成整组键。如果您对包含数百万行的数据库使用绑定哈希,这一点可能很重要。您不想一次生成整个密钥列表并耗尽您的物理内存。在这种情况下,每个都充当迭代器,而键实际上在循环开始之前生成整个数组。

因此,“each”唯一真正有用的地方是当散列非常大时(与可用内存相比)。只有当散列本身不存在于内存中时,才可能发生这种情况,除非您正在对手持式数据收集设备或内存较小的设备进行编程。

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

关于这个主题的一些不同的想法:

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

我也总是使用方法2。使用每个的唯一好处是,如果您只是读取(而不是重新分配)散列条目的值,则不会不断取消对散列的引用。

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

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

我通常使用 keys 我想不起我上次使用或阅读的用途是什么时候 each.

别忘了 map, ,取决于你在循环中做什么!

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

我想说:

  1. 使用对大多数人来说最容易阅读/理解的内容(所以我认为通常是键)
  2. 在整个代码库中一致使用您决定的任何内容。

这有两个主要优点:

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

我不认为使用每个键会更昂贵,因此不需要在代码中为同一事物使用两种不同的构造。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top