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