什么是Python迭代器的Perl版本?

时间:2010-09-23 04:14:09

标签: python perl iterator

我在工作中学习Perl并享受它。我通常用Python做我的工作,但老板想要Perl。

Python和Perl中的大多数概念匹配得很好:Python dictionary = Perl hash; Python元组= Perl列表; Python list = Perl数组;等。

问题:是否存在Iterator / Generator的Python形式的Perl版本?

示例:生成Fibonacci数字的经典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中找到它...除了在循环或递归中编写它或生成一个巨大的静态列表,我该怎么做(对于ex)用Perl写它的Fibonacci子程序?是否有我错过的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 个答案:

答案 0 :(得分:36)

迭代器的概念在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 );

答案 1 :(得分:34)

对于比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);

在每种情况下,生成器都是惰性的,在创建时不计算任何内容,然后仅计算满足切片所需的那些值。 Fibonacci序列的递归定义多次调用自身,因此cache函数用于确保每个值仅计算一次。

您还可以将生成器用作迭代器:

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

$fib->next也可以写成$fib->()。由于生成器仍然是随机访问,因此您可以$fib->reset()$fib->index = 10;

如果您有任何问题,请与我们联系。

更新

我发布了一个新版本的模块(0.80),可以更容易地在生成器中使用迭代算法。这是一个与OP的例子密切相关的例子:

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};

如果你{sub}之前或之前use bigint;,你当然可以:

say for @{fibonacci 400}; # or more

答案 2 :(得分:16)

优秀的Higher-Order Perl书(在指定链接上免费提供)包含大量有关相关主题的信息,特别是有关迭代器的整章。通过“更高阶”,作者暗示使用Perl的能力作为具有一流功能的函数式语言来实现各种很酷的东西。它确实是一本非常好的书 - 我阅读了大部分内容,关于迭代器和流的章节非常棒。如果您打算编写Perl代码,我强烈建议您至少浏览一下。

答案 3 :(得分:8)

有一种类似的方法来生成迭代器/生成器,但它不是像Python一样的“一等公民”。

在Perl中,如果你没有看到你想要的东西(在强制性之后前往CPAN 第一次!),你可以自己滚动类似于基于Perl闭包和匿名子例程的Python迭代器。

考虑:

use strict; use warnings;

sub fibo {
    my ($an, $bn)=(1,0);
    my $mag=(shift || 1);
    my $limit=10**$mag;
    my $i=0;

    return sub {
        ($an, $bn)=($bn, $an+$bn);      
        return undef if ($an >=$limit || wantarray );
        return $an;
    }
}

my $num;
my $iter=fibo(15);
while (defined($num=$iter->()) ) { printf "%17d\n", $num; }

fibo维护一个Perl closure,允许维护持久变量。你可以通过一个类似于C / C ++的模块来做同样的事情。在fibo内部,匿名子例程执行返回下一个数据项的工作。

引用Perl Bible“在你了解标量和列表上下文之间的差异之前,你会很痛苦” - 第69页(强烈推荐的书btw ......)

在这种情况下,annon sub仅返回单个值。 Perl中唯一可以在标量上下文中工作的循环机制是while;其他人试图填写清单,然后继续我的想法。因此,如果你在列表上下文中调用anon sub,它将尽职地返回下一个fibonacci数,不像Python的迭代器那样,循环将终止。这就是我放return undef if .... wantarray的原因,因为它在列表上下文中不起作用。

有办法解决这个问题。实际上,您可以编写类似map foreach等的子例程,但它并不像Python的收益那么简单。在foreach循环中需要一个额外的函数才能使用。权衡是Perl方法具有巨大的力量和灵活性。

你可以在Mark Jason Dominus的优秀书籍“Higher Order Perl”中了解更多关于Perl迭代器的信息Chapter 4 is all about Interators brian d foy在Perl Review中对Interators也有很好的article

答案 4 :(得分:6)

有一个很好的实用示例here和一篇PDF文章here ...但我在Perl中太生疏了,试图直接实现你的挑战(正如你所看到的,这两个例子)并且PDF中的方法使用不那么直接的方法。)

答案 5 :(得分:4)

这是一个量身定制的回应,以便与最初提出的问题保持一致。

任何实现延迟列表的perl模块(例如List :: Gen,Memoize等)也允许你提供自己的生成器子程序(我不是说像Python中的'generator')将允许你这样做在这个例子中显示。懒惰地生成列表的模块称为Alef。

#!/usr/bin/perl -w

use strict; use warnings; 
use Alef;

my $fibo;

BEGIN {

    my ($a, $b) = (0, 1);

    $fibo = sub {
        ($a, $b) = ($b, $a+$b);
        $a;
    }
}

my $fibonacci = new Alef($fibo);

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

这是输出:

[spl @ briareus~] $ ./fibo.pl 0 1 1 2 3 五 8 13 21 34 55 89 144 233 377

这里使用的懒惰列表模块幕后没有任何神奇的事情发生。这就是Alef的子程序看起来的样子。

sub take {

    my ($self,$n) = (@_);

    my @these = ();

    my $generator = $self->{'generator'};
    for (1..$n){
        push(@these,$self->{'this'});
        $self->{'this'} = &$generator($self->{'this'});
    }
    @these;
}

答案 6 :(得分:3)

在这种情况下,可以使用memoization。

use strict;
use warnings;

use Memoize;
memoize('fib');

foreach my $i (1..15) {
  print "$i -> ",fib($i),"\n";
}

sub fib {
  my $n = shift;
  return $n if $n < 2;
  fib($n-1) + fib($n-2);
}

答案 7 :(得分:3)

CPAN上有一些迭代器/生成器模块可以提供帮助。以下是您直接翻译为Coro::Generator模块的示例:

use 5.016;
use warnings;
use Coro::Generator;

sub gen_fibonacci {
    my $mag = shift;
    generator {
        my ($a, $b) = (0, 1);
        while ($a <= 10 ** $mag) {
            yield $a;
            ($a, $b) = ($b, $a + $b);
        }   
        yield undef;  # stop it!
    };  
}   

my $fibonacci = gen_fibonacci(15);

while (defined (my $number = $fibonacci->())) {
    printf "%17d\n", $number;
}