代码之家  ›  专栏  ›  技术社区  ›  the wolf

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

  •  39
  • the wolf  · 技术社区  · 14 年前

    我在工作中学习了Perl并很享受它。我通常在python中工作,但boss需要perl。

    python和perl中的大多数概念都很好地匹配:python dictionary=perl hash;python tuple=perl list;python list=perl array;等等。

    问题:是否有python形式的perl版本 Iterator /发电机?

    一个例子:生成斐波那契数的经典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“lists”看起来更静态——更像是一个python元组。在Perl中,可以 foreach 是动态的还是仅基于静态列表?

    迭代器的python形式是我已经习惯的一种形式,我没有发现它在perl中有文档记录。除了用循环、递归或生成一个巨大的静态列表来编写它之外,我(对于ex)如何用Perl编写fibonacci子例程呢?有珍珠吗 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 回复  |  直到 11 年前
        1
  •  33
  •   Eric Strom    14 年前

    为了比Python的生成器更灵活的解决方案,我编写了这个模块 List::Gen 在提供随机访问延迟生成器阵列的CPAN上:

    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),它使得在生成器中使用迭代算法更容易。下面是一个与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};
    

    如果你 use bigint; 在潜艇之前或顶部,你当然可以:

    say for @{fibonacci 400}; # or more
    
        2
  •  35
  •   Axeman maxelost    14 年前

    迭代器的概念在Perl中有点不同。您基本上希望在持久变量上返回一个一次性的子例程“closed”。

    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 );
    
        3
  •  16
  •   Eli Bendersky    14 年前

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

        4
  •  8
  •   dawg    14 年前

    有一个类似的方法来生成迭代器/生成器,但它并不像在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++的模块来做同样的事情。里面 菲博 匿名子例程执行返回下一个数据项的工作。

    引用自 Perl Bible “在你了解标量和列表上下文之间的区别之前,你会很痛苦的”-p 69(一本强烈推荐的书btw…)

    在这种情况下,annon sub只返回一个值。我在Perl中所知道的唯一可以在标量上下文中工作的循环机制是 while 我想,其他人在继续之前会先把名单填上。因此,如果在列表上下文中调用anon sub,它将尽职尽责地返回下一个fibonacci编号,而不像python对迭代器的编号,循环将终止。这就是为什么我把 return undef if .... wantarray 因为它不能在写的列表上下文中工作。

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

    您可以在MarkJasonDominos的优秀著作“高阶Perl”中了解更多关于Perl迭代器的信息。 Chapter 4 is all about Interators 布莱恩·福伊也有一个优秀的 article Perl评论中的交互程序。

        5
  •  6
  •   Alex Martelli    14 年前

    有一个很好的实例 here 还有一篇pdf文章 here …但是我对Perl过于生疏,无法直接实现您的挑战(正如您将看到的,PDF中的示例和方法都使用了一种不太直接的方法)。

        6
  •  4
  •   Steve Laesch    13 年前

    这里是一个与最初提出的问题紧密一致的回答。

    任何实现惰性列表的Perl模块(例如,list::gen、memoize等)也允许您提供自己的生成器子例程(我的意思不是“generator”,如python中所示),这将允许您执行本例中所示的操作。在这里,懒惰地生成列表的模块称为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 零 一 一 二 三 五 八 十三 二十一 三十四 五十五 八十九 一百四十四 二百三十三 三百七十七

    使用这里使用的懒惰列表模块,在幕后没有什么神奇的事情发生。这就是alef的take子例程的样子。

    sub take {
    
        my ($self,$n) = (@_);
    
        my @these = ();
    
        my $generator = $self->{'generator'};
        for (1..$n){
            push(@these,$self->{'this'});
            $self->{'this'} = &$generator($self->{'this'});
        }
        @these;
    }
    
        7
  •  3
  •   Alexandr Ciornii    14 年前

    在这种情况下,可以使用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);
    }
    
        8
  •  3
  •   draegtun    11 年前

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