2008-08-06 39 views
80

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

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

# Method 2 
foreach my $key (keys(%hash)) { 
    # Something 
} 

回答

161

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

如果你只是想的钥匙,不打算永远阅读任何值,使用键():

foreach my $key (keys %hash) { ... } 

如果你只是想要的值,使用价值():

foreach my $val (values %hash) { ... } 

如果您需要的按键值,使用每个():

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

如果您打算以任何方式更改散列的密钥,除了用于在迭代过程中删除当前密钥,则不得使用each()。例如,该代码以创建一组新的大写键的具有双值工作正常使用箭头键():

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

foreach my $k (keys %h) 
{ 
    $h{uc $k} = $h{$k} * 2; 
} 

产生预期结果散列:

(a => 1, A => 2, b => 2, B => 4) 

但是使用每个()做同样的事情:

%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 
+6

请添加一个void-context键%h;在每个循环之前使用迭代器安全地显示。 – ysth 2008-12-01 03:01:38

+5

每个人都有另一个警告。迭代器绑定到散列,而不是上下文,这意味着它不是可重入的。例如,如果循环遍历散列,并且打印散列perl将在内部重置迭代器,从而使该代码无止境地循环: my%hash =(a => 1,b => 2,c => 3); (my($ k,$ v)= each%hash){ print%hash; } 阅读更多在http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html – Rawler 2014-04-08 09:01:43

3

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

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

4

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

1

我通常使用keys,我不能想想我上次用过或读过each的用法。

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

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

不使用地图,除非你想返回值 – 2009-11-09 23:11:44

12

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

因此,唯一的地方“每个”是真正的用途是什么时候散列非常大(与可用内存相比)。这只有在散列本身不存在于内存本身时才会发生,除非您正在编程手持数据收集设备或具有小内存的东西。

如果内存不是问题,通常地图或按键范例是更加优先和更容易阅读的范例。

4

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

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

“不”对于密钥是真的“,而是:它不适用于密钥,并且任何删除都是安全的。您使用的措辞意味着使用密钥时删除任何内容都是不安全的。 – ysth 2008-12-01 02:58:15

+2

回复:“对于任何哈希迭代器都没有任何不安全性”,另一个危险是假设迭代器在开始每个循环之前就开始了,正如其他人提到的那样。 – ysth 2008-12-01 02:59:38

20

一件事是,它有 加入“国家”的散列(哈希必须记住 “下一个”关键是什么)的副作用。当使用上面发布的代码片段(如 )来遍历整个散列时,这通常不是 问题。当使用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"; 
} 

此打印:

found key bar 
found key baz 
the hash contains: 
quux => 4 
foo => 1 

发生了什么事键 “栏” 巴兹“他们还在那里,但 第二each开始,在第一个?当它到达散列末尾时停止,因此我们从未在第二个循环中看到它们。

18

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第一(因为这重置迭代器)。请参阅documentation for each

-2

我woudl说:

  1. 使用任何最简单的方法来读取/理解大多数人(这样的键,通常情况下,我要说的)
  2. 使用任何你始终决定throught整个代码库。

这得到2个主要优点:

  1. 很容易发现“共同”的代码,所以你可以重新因素纳入功能/ methiods。
  2. 未来的开发者维护起来会更容易。

我不认为在每个按键上使用按键会更昂贵,所以在代码中不需要两个不同的构造。