【问题标题】:What's the safest way to iterate through the keys of a Perl hash?遍历 Perl 哈希键的最安全方法是什么?
【发布时间】:2010-09-05 09:44:36
【问题描述】:

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

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

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

【问题讨论】:

    标签: perl hash iteration each


    【解决方案1】:

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

    如果您只想要键并且不打算读取任何值,请使用 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
    

    【讨论】:

    • 请添加一个空上下文键 %h;在每个循环之前使用迭代器安全地显示。
    • 每个都有另一个警告。迭代器绑定到哈希,而不是上下文,这意味着它不可重入。例如,如果您遍历一个哈希,并打印哈希 perl 将在内部重置迭代器,使此代码无限循环: my %hash = ( a => 1, b => 2, c => 3, ); while ( my ($k, $v) = each %hash ) { print %hash; } 阅读更多blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
    【解决方案2】:

    使用 each 时您应该注意的一件事是它具有 将“状态”添加到散列的副作用(散列必须记住 “下一个”键是什么)。使用上面发布的 sn-ps 之类的代码时, 一次遍历整个哈希,这通常不是 问题。但是,您将遇到难以追查的问题(我从 经验 ;),当使用 each 和类似的语句时 lastreturn 在您之前退出 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 从第一个停止的地方开始,并在到达哈希末尾时停止,所以我们在第二个循环中永远不会看到它们。

    【讨论】:

      【解决方案3】:

      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

      【讨论】:

        【解决方案4】:

        使用 each 语法将防止一次生成整个密钥集。如果您使用绑定散列到具有数百万行的数据库,这可能很重要。您不想一次生成整个密钥列表并耗尽您的物理内存。在这种情况下,每个都用作迭代器,而键实际上是在循环开始之前生成整个数组。

        因此,“每个”唯一真正有用的地方是当哈希非常大时(与可用内存相比)。只有当哈希本身并不存在于内存中时才会发生这种情况,除非您正在编写手持数据收集设备或内存小的东西。

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

        【讨论】:

          【解决方案5】:

          关于这个话题的一些其他想法:

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

          【讨论】:

          • Re“不适用于键”,而是:它不适用于键,任何删除都是安全的。您使用的措辞暗示在使用键时删除任何内容是绝对不安全的。
          • Re:“没有任何不安全的哈希迭代器”,另一个危险是假设迭代器在开始每个循环之前处于开头,正如其他人提到的那样。
          【解决方案6】:

          我也总是使用方法 2。使用 each 的唯一好处是,如果您只是读取(而不是重新分配)哈希条目的值,则不会一直取消引用哈希。

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              【解决方案8】:

              我通常使用keys,但我想不起我上次使用或阅读each的使用。

              不要忘记map,这取决于您在循环中所做的事情!

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

              【讨论】:

              • 除非你想要返回值,否则不要使用 map
              【解决方案9】:

              我想说:

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

              这有两个主要优点:

              1. 更容易发现“通用”代码,因此您可以重新考虑函数/方法。
              2. 以后的开发人员更容易维护。

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

              【讨论】:

              • 随着keys 内存使用量增加hash-size * avg-key-size。鉴于密钥大小仅受内存限制(因为它们只是数组元素,例如引擎盖下的“它们的”对应值),在某些情况下,它在内存使用和时间上可能会禁止更昂贵用于制作副本。
              猜你喜欢
              • 2011-05-16
              • 2013-01-16
              • 2022-07-21
              • 2012-06-23
              • 2013-12-30
              • 2016-08-20
              • 2011-10-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多