Perl的每个功能都值得使用吗?

时间:2010-03-07 14:10:25

标签: perl hash iterator each

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一般?

8 个答案:

答案 0 :(得分:10)

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

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也将允许对数组进行迭代。

答案 1 :(得分:8)

我发现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循环将永远持续下去,这绝对是一个严重的错误。

答案 2 :(得分:8)

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

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

答案 3 :(得分:7)

每个都不仅值得使用,如果你想循环所有对于内存来说太大的连接哈希,这几乎是强制性的。

在开始循环之前,void-context keys()(或值,但一致性很好)是唯一必需的“解决方法”;你有什么理由寻找其他的解决方法吗?

答案 4 :(得分:2)

使用keys()函数重置迭代器。有关详细信息,请参阅faq

答案 5 :(得分:2)

each有一个可以伤害你的buit-in,隐藏的全局变量。除非您需要此行为,否则使用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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

如果你在一个绑定的哈希中迭代,那么每个()可以更有效率,例如一个包含数百万个密钥的数据库;这样你就不必在内存中加载所有密钥了。