【问题标题】:Can I copy a hash without resetting its "each" iterator?我可以在不重置其“每个”迭代器的情况下复制哈希吗?
【发布时间】:2012-06-10 21:10:35
【问题描述】:

我正在使用each 来遍历 Perl 哈希:

while (my ($key,$val) = each %hash) {
   ...
}

然后发生了一些有趣的事情,我想打印出哈希。一开始我会考虑这样的事情:

while (my ($key,$val) = each %hash) {
   if (something_interesting_happens()) {
      foreach my $k (keys %hash) { print "$k => $hash{$k}\n" }
   }
}

但这行不通,因为每个人都知道在哈希上调用keys(或values)会重置用于each 的内部迭代器,并且我们可能会陷入无限循环。例如,这些脚本将永远运行:

perl -e '%a=(foo=>1); while(each %a){keys %a}'
perl -e '%a=(foo=>1); while(each %a){values %a}'

没问题,我想。我可以复制哈希,然后打印出来。

   if (something_interesting_happens()) {
      %hash2 = %hash;
      foreach my $k (keys %hash2) { print "$k => $hash2{$k}\n" }
   }

但这也行不通。这也会重置 each 迭代器。事实上,在列表上下文中对%hash 的任何使用似乎都会重置其each 迭代器。所以这些也永远运行:

perl -e '%a=(foo=>1); while(each %a){%b = %a}'
perl -e '%a=(foo=>1); while(each %a){@b = %a}'
perl -e '%a=(foo=>1); while(each %a){print %a}'

这在任何地方都有记录吗? perl 可能需要使用相同的内部迭代器将哈希的内容推送到返回堆栈是有道理的,但我也可以想象不需要这样做的哈希实现。

更重要的是,有什么方法可以做我想做的事吗?要在不重置 each 迭代器的情况下获取哈希的所有元素?


这也表明您也不能在 each 迭代中调试哈希。考虑在以下位置运行调试器:

%a = (foo => 123, bar => 456);
while ( ($k,$v) = each %a ) {
    $DB::single = 1;
    $o .= "$k,$v;";
}
print $o;

只需检查调试器停止的哈希值(例如,输入p %ax %a),您将更改程序的输出。


更新:我上传了Hash::SafeKeys 作为此问题的通用解决方案。感谢@gpojd 为我指明了正确的方向,感谢@cjm 的建议,使解决方案变得更加简单。

【问题讨论】:

  • 最近遇到了同样的问题。发现 Hash::SafeKeys 非常慢并且取决于哈希大小,而 Hash::StoredIterator + 默认键的性能要好得多。
  • @AlexandrEvstigneev - 谢谢,这很有趣。我会更新Hash::SafeKeys

标签: perl


【解决方案1】:

不是真的。 each 非常脆弱。它将迭代状态存储在迭代哈希本身上,当 perl 的其他部分需要时,该状态会被重用。更安全的是忘记它的存在,并始终从 keys %hash 的结果中迭代您自己的列表,因为列表上的迭代状态在词法上存储为 for 循环本身的一部分,因此可以免受损坏其他的东西。

【讨论】:

    【解决方案2】:

    我们不要忘记,当您进入while 循环时,keys %hash 已经定义好了。可以简单地将密钥保存到一个数组中以供以后使用:

    my @keys = keys %hash;
    
    while (my ($key,$val) = each %hash) {
    
        if (something_interesting_happens()) {
    
            print "$_ => $hash{$_}\n" for @keys;
        }
    }
    

    缺点:

    • 不那么优雅(主观)
    • 如果修改了%hash,它将无法工作(但是为什么首先要使用each?)

    优势:

    • 通过避免哈希复制使用更少的内存

    【讨论】:

      【解决方案3】:

      你试过Storable'sdclone复制吗?大概是这样的:

      use Storable qw(dclone);
      my %hash_copy = %{ dclone( \%hash ) };
      

      【讨论】:

      • perl -MStorable=dclone -e '%a=(foo=>1);while(each%a){dclone \%a}' 在我尝试时不会无限循环。
      • 聪明! store_hash function in Storable 保存迭代器的状态,在存储哈希时遍历哈希,完成后恢复迭代器状态。
      • 当哈希包含 GLOB 和 CODE 值时,这将失败。但这绝对让我走上了正确的轨道。我只需要复制store_hash,然后扔掉大约一半。
      • @mob,您可以编写一个模块来保存和恢复迭代器状态,而不是制作副本。然后你可以保存迭代器,转储原始哈希,并恢复迭代器。
      • @cjm - 不错!然后我可以扔掉 90% 的 store_hash
      【解决方案4】:

      这个哈希有多大?遍历它需要多长时间,以便您关心访问的时间?

      只需设置一个标志并在迭代结束后执行操作:

      my $print_it;
      while (my ($key,$val) = each %hash) {
          $print_it = 1 if something_interesting_happens();
          ...
      }
      
      if ($print_it) {
          foreach my $k (keys %hash) { print "$k => $hash{$k}\n" }
      }
      

      虽然也没有理由不在打印输出代码中使用each,除非您打算按键或其他方式进行排序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-28
        • 2020-07-03
        • 2012-12-27
        • 2019-01-12
        • 2010-12-03
        • 2013-07-14
        • 2016-11-07
        • 1970-01-01
        相关资源
        最近更新 更多