我在工作中学习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"; }
提前感谢对新手的善意......
答案 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)
答案 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;
}