【问题标题】:Is perl's each function worth using?perl 的 each 函数值得使用吗?
【发布时间】:2011-01-24 16:30:14
【问题描述】:

来自perldoc -f each 我们读到:

每个哈希都有一个迭代器,由程序中的所有eachkeysvalues函数调用共享;它可以通过读取散列中的所有元素或通过评估keys HASHvalues HASH 来重置。

当您离开包含each() 的作用域时,迭代器不会重置,这可能会导致错误:

my %h = map { $_, 1 } qw(1 2 3);
while (my $k = each %h) { print "1: $k\n"; last }
while (my $k = each %h) { print "2: $k\n"       }

输出:

1: 1
2: 3
2: 2

此行为的常见解决方法是什么?一般情况下是否值得使用each

【问题讨论】:

  • 我想常见的解决方法包括评估 keys HASHvalues HASH

标签: perl hash iterator each


【解决方案1】:

我认为只要您意识到这一点,它就值得使用。当您在迭代中同时需要键和值时,它是理想的:

while (my ($k,$v) = each %h) {
    say "$k = $v";
}

在您的示例中,您可以通过添加 keys %h; 来重置迭代器,如下所示:

my %h = map { $_ => 1 } qw/1 2 3/;
while (my $k = each %h) { print "1: $k\n"; last }
keys %h;  # reset %h
while (my $k = each %h) { print "2: $k\n" }

从 Perl 5.12 开始,each 也将允许对数组进行迭代。

【讨论】:

    【解决方案2】:

    我发现each 对于这样的成语非常方便:

    my $hashref = some_really_complicated_method_that_builds_a_large_and_deep_structure();
    while (my ($key, $value) = each %$hashref)
    {
        # code that does stuff with both $key and $value
    }
    

    将该代码与此进行对比:

    my $hashref = ...same call as above
    foreach my $key (keys %$hashref)
    {
        my $value = $hashref->{$key};
        # more code here...
    }
    

    在第一种情况下,$key$value 都可以立即用于循环体。在第二种情况下,必须先获取$value。此外,$hashref 的键列表可能非常庞大,占用内存。这有时是一个问题。 each 不会产生这样的开销。

    然而,each 的缺点并没有立即显现:如果提前退出循环,哈希的迭代器不会被重置。此外(我发现这个更严重,甚至更不明显):你不能在这个循环中调用keys()values() 或另一个each()。这样做会重置迭代器,并且您将失去在 while 循环中的位置。 while 循环将永远持续下去,这绝对是一个严重的错误。

    【讨论】:

      【解决方案3】:

      each 太危险了,永远不能使用,许多风格指南完全禁止使用它。危险在于,如果each 的循环在散列结束之前中止,则下一个循环将从那里开始。这可能会导致非常难以重现的错误;程序的一个部分的行为将取决于程序的完全不相关的其他部分。 可能会使用each 对,但是曾经编写的每个可能使用你的散列(或hashref;它是相同的)的模块呢?

      keysvalues 始终是安全的,所以请使用它们。 keys 使得以确定的顺序遍历散列变得更容易,无论如何,这几乎总是更有用。 (for my $key (sort keys %hash) { ... })

      【讨论】:

      • 使用大量全局哈希,是吗?
      • 不管它是否是全局的。甚至一个类的私有属性也容易受到这个问题的影响。任何返回哈希引用的东西都会受到影响。
      【解决方案4】:

      each 不仅值得使用,而且如果你想遍历所有对内存来说太大的绑定哈希,它几乎是强制性的。

      在开始循环之前的 void-context keys() (或值,但一致性很好)是唯一必要的“解决方法”;您是否出于某种原因正在寻找其他解决方法?

      【讨论】:

      • 好点!这是我能想到的使用each 的最好(唯一?)理由。
      【解决方案5】:

      使用keys() 函数重置迭代器。请参阅faq 了解更多信息

      【讨论】:

        【解决方案6】:

        each 有一个内置的、隐藏的全局变量,它会伤害你。除非您需要这种行为,否则只使用keys 会更安全。

        考虑这个我们想要对 k/v 对进行分组的示例(是的,我知道 printf 会做得更好):

        #!perl
        
        use strict;
        use warnings;
        
        use Test::More 'no_plan';
        
        {   my %foo = map { ($_) x 2 } (1..15);
        
            is( one( \%foo ), one( \%foo ), 'Calling one twice works with 15 keys' );
            is( two( \%foo ), two( \%foo ), 'Calling two twice works with 15 keys' );
        }
        
        {   my %foo = map { ($_) x 2 } (1..105);
        
            is( one( \%foo ), one( \%foo ), 'Calling one twice works with 105 keys' );
            is( two( \%foo ), two( \%foo ), 'Calling two twice works with 105 keys' );
        }
        
        
        sub one {
            my $foo = shift;
        
            my $r = '';
        
            for( 1..9 ) {
                last unless my ($k, $v) = each %$foo;
        
                $r .= "  $_: $k -> $v\n";
            }
            for( 10..99 ) {
                last unless my ($k, $v) = each %$foo;
        
                $r .= " $_: $k -> $v\n";
            }
        
            return $r;
        }
        
        sub two {
            my $foo = shift;
        
            my $r = '';
        
            my @k = keys %$foo;
        
            for( 1..9 ) {
                last unless @k;
                my $k = shift @k;
        
                $r .= "  $_: $k -> $foo->{$k}\n";
            }
            for( 10..99 ) {
                last unless @k;
                my $k = shift @k;
        
                $r .= "  $_: $k -> $foo->{$k}\n";
            }
        
            return $r;
        }
        

        在实际应用程序中调试上述测试中显示的错误将非常痛苦。 (为了获得更好的输出,请使用 Test::Differences eq_or_diff 而不是 is。)

        当然one() 可以通过使用keys 清除子程序开始和结束处的迭代器来修复。如果你记得。如果你所有的同事都记得。只要没有人忘记就绝对安全。

        我不了解你,但我会坚持使用 keysvalues

        【讨论】:

          【解决方案7】:

          最好使用 nameeach。如果您的意思是“给我第一个键值对”或“给我前两个对”或其他什么,那么使用它可能是错误的。请记住,这个想法足够灵活,每次调用它时,都会得到 next 对(或标量上下文中的键)。

          【讨论】:

            【解决方案8】:

            each() 如果您正在遍历绑定的哈希,例如包含数百万个键的数据库,则效率会更高;这样您就不必在内存中加载所有键。

            【讨论】:

              猜你喜欢
              • 2011-01-20
              • 2014-11-30
              • 2023-04-11
              • 2011-10-19
              • 1970-01-01
              • 2015-05-14
              • 2018-04-16
              • 1970-01-01
              相关资源
              最近更新 更多