迭代Perl哈希键的最安全的方法是什么?

Rud*_*ski 102 iteration perl hash each

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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*usa 193

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

如果您只想要键并且不打算读取任何值,请使用keys():

foreach my $key (keys %hash) { ... }
Run Code Online (Sandbox Code Playgroud)

如果您只想要值,请使用values():

foreach my $val (values %hash) { ... }
Run Code Online (Sandbox Code Playgroud)

如果需要键值,请使用each():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }
Run Code Online (Sandbox Code Playgroud)

如果您计划以任何方式更改哈希的键,除了在迭代期间删除当前键,那么您不能使用each().例如,使用keys()创建一组具有doubled值的新大写键的代码可以正常工作:

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}
Run Code Online (Sandbox Code Playgroud)

产生预期的结果哈希:

(a => 1, A => 2, b => 2, B => 4)
Run Code Online (Sandbox Code Playgroud)

但是使用each()来做同样的事情:

%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}
Run Code Online (Sandbox Code Playgroud)

以难以预测的方式产生不正确的结果.例如:

(a => 1, A => 2, b => 2, B => 8)
Run Code Online (Sandbox Code Playgroud)

但是,这是安全的:

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}
Run Code Online (Sandbox Code Playgroud)

所有这些都在perl文档中描述:

% perldoc -f keys
% perldoc -f each
Run Code Online (Sandbox Code Playgroud)

  • 请添加一个void-context键%h; 在每个循环之前使用迭代器安全地显示. (6认同)
  • 每个都有另一个警告.迭代器绑定到哈希,而不是上下文,这意味着它不是可重入的.例如,如果你遍历一个哈希,并打印哈希perl将在内部重置迭代器,使这个代码循环无休止:我的%哈希=(a => 1,b => 2,c => 3,); while(my($ k,$ v)=每个%hash){print%hash; 阅读更多信息,请访问http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html (5认同)

8je*_*ean 25

在使用时你应该注意的一件事each是它具有向你的哈希添加"状态"的副作用(哈希必须记住"下一个"键是什么).当使用像上面发布的代码片段一样遍历整个哈希值的代码时,这通常不是问题.但是,在处理完所有密钥之前,在each与语句一起使用 lastreturnwhile ... 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";
}
Run Code Online (Sandbox Code Playgroud)

这打印:

found key bar
found key baz
the hash contains:
quux => 4
foo => 1
Run Code Online (Sandbox Code Playgroud)

按键"bar"和baz"发生了什么??它们仍在那里,但是第二个each从第一个停止的地方开始,当它到达散列的末尾时停止,所以我们永远不会在第二个循环中看到它们.


Dar*_*yer 20

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";
}
Run Code Online (Sandbox Code Playgroud)

如果您需要确保each获取所有键和值,则需要确保使用keysvalues首先使用(因为它会重置迭代器).请参阅每个文档.


小智 13

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

因此,"每个"实际使用的唯一地方是散列非常大(与可用内存相比).只有在散列本身不存在于内存中时,才会发生这种情况,除非您正在编程手持数据采集设备或内存较小的内容.

如果内存不是问题,那么通常地图或键范例是更容易阅读和更容易阅读的范例.


Mic*_*man 6

关于这个主题的一些杂项想法:

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

  • Re"不适用于密钥",而是:它不适用于密钥,任何删除都是安全的.您使用的措辞意味着在使用密钥时删除任何内容永远不会安全. (2认同)
  • Re:"没有任何关于任何散列迭代器的不安全",另一个危险是假设迭代器在开始每个循环之前处于开头,正如其他人提到的那样. (2认同)