Python迭代器在Perl中的对应是什么?

41 投票
8 回答
8605 浏览
提问于 2025-04-16 04:29

我在工作中学习Perl,觉得挺有意思的。平时我主要用Python,但老板想用Perl。

Python和Perl之间有很多概念是相似的:Python的字典对应Perl的哈希;Python的元组对应Perl的列表;Python的列表对应Perl的数组,等等。

我想问:在Perl中有没有类似Python的迭代器或生成器的东西?

举个例子:用Python生成斐波那契数列的经典方法是:

#!/usr/bin/python

def fibonacci(mag):
     a, b = 0, 1
     while a<=10**mag:
         yield a
         a, b = b, a+b

for number in fibonacci(15):  
     print "%17d" % number

迭代器在需要从一个很大的列表中生成一个子集时也很有用。Perl的“列表”看起来更静态,更像Python的元组。在Perl中,foreach能否动态生成,还是只能基于一个静态列表?

我习惯了Python的迭代器形式,但在Perl中找不到相关的文档……除了用循环或递归,或者生成一个巨大的静态列表,我该怎么在Perl中写斐波那契的子程序?难道我错过了Perl中的yield吗?

具体来说——我该怎么写这个:

#!/usr/bin/perl
use warnings; use strict; # yes -- i use those!

sub fibonacci {
   # What goes here other than returning an array or list? 
}

foreach my $number (fibonacci(15)) { print $number . "\n"; }

提前感谢大家对新手的包容……

8 个回答

16

这本很棒的《高阶Perl》书(可以在链接里免费获取)包含了很多相关主题的信息,特别是有一整章讲解迭代器。这里的“高阶”是指作者利用Perl作为一种函数式编程语言的能力,使用一等函数来实现各种有趣的功能。这真的是一本非常好的书——我读过大部分内容,关于迭代器和流的章节特别精彩。如果你打算写Perl代码,我强烈建议你至少浏览一下这本书。

40

在Perl中,迭代器的概念有点不同。你基本上是想返回一个只用一次的子程序,这个子程序会“封闭”在一些持久的变量里。

use bigint;
use strict;
use warnings;

sub fibonacci {
    my $limit = 10**( shift || 0 );
    my ( $a, $b ) = ( 0, 1 );
    return sub { 
        return if $a > $limit;
        ( my $r, $a, $b ) = ( $a, $b, $a + $b );
        return $r;
    };
}
my $fit = fibonacci( 15 );
my $n = 0;
while ( defined( my $f = $fit->())) { 
     print "F($n): $f\n";
     $n++;
}

如果你不喜欢用while循环的话,这里有两种更简洁的写法,基本上可以实现逐个处理每个项目的循环:

sub iterate ($$) {
    my $iter   = shift;
    my $action = shift;
    while ( defined( my $nextval = $iter->())) { 
        local *_ = \$nextval;
        $action->( $_ );
    }
    return;
}

iterate fibonacci( 15 ) => sub { print "$_\n"; };

sub iter (&$) { 
    my $action = shift;
    my $iter   = shift;
    while ( defined( my $nextval = $iter->())) { 
        local *_ = \$nextval;
        $action->( $_ );
    }
    return;
}

iter { print "$_\n" } fibonacci( 15 );
35

为了提供比Python的生成器更灵活的解决方案,我在CPAN上写了一个模块List::Gen,它提供了可以随机访问的懒生成器数组:

use List::Gen;

my $fib; $fib = cache gen {$_ < 2  ? $_ : $$fib[$_ - 1] + $$fib[$_ - 2]};

say "@$fib[0 .. 15]";  #  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610

因为生成器看起来像数组,所以它们可以和普通的Perl代码无缝结合。还有一种面向对象的方法:

my $fib; $fib = cache gen {$_ < 2 ? $_ : $fib->get($_ - 1) + $fib->get($_ - 2)};

say join ' ' => $fib->slice(0 .. 15);

在这两种情况下,生成器都是懒惰的,创建时什么都不计算,只有在需要满足特定请求时才会计算那些值。斐波那契数列的递归定义会多次调用自身,所以使用了cache函数来确保每个值只计算一次。

你还可以把生成器当作迭代器使用:

while (my $num = $fib->next) {
    last if $num > 10**15;
    print "$_\n";
}

$fib->next也可以写成$fib->()。因为生成器仍然可以随机访问,所以你可以使用$fib->reset()或者$fib->index = 10;

如果你有任何问题,请告诉我。

更新:

我发布了模块的新版本(0.80),使得在生成器中使用迭代算法变得更简单。这里有一个例子,和原始问题的例子非常相似:

use List::Gen '*';

sub fibonacci {
    my $limit   = 10**shift;
    my ($x, $y) = (0, 1);

    While {$_ < $limit} gather {
        ($x, $y) = ($y, take($x) + $y)
    }
}

say for @{fibonacci 15};

如果你在子程序之前或顶部使用use bigint;,当然可以:

say for @{fibonacci 400}; # or more

撰写回答