【问题标题】:What is the Perl version of a Python iterator?Python 迭代器的 Perl 版本是什么?
【发布时间】:2011-04-16 01:56:54
【问题描述】:

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

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

问题:有没有Iterator/Generator 的 Python 形式的 Perl 版本?

一个例子:生成斐波那契数的经典 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 可以是动态的还是仅基于静态列表?

Iterator 的 Python 形式是我已经习惯的一种形式,我没有发现它在 Perl 中记录... ex) 在 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"; }

在此先感谢您对新手的善意...

【问题讨论】:

    标签: python perl iterator


    【解决方案1】:

    迭代器的概念在 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,它是 10**limit,而不仅仅是 15 个步骤...
    • @drewk:修复它。为了测试它,我省略了指数,忘记放回去了。
    • @Eric Strom:我认为因为您需要第二个参数,而且多年来我发现'*' 是 Perl 抱怨最少的一个。它不会说“预期的 glob 或裸词,但得到 x”,但如果我不输入第二个参数,它会抱怨,如果我通过某种形式的子例程,它不会抱怨。
    • 在这些情况下,我通常会使用 ($) 上下文,这样可以避免任何错误,例如传递您希望作为函数调用但拼写错误并作为字符串静默传递的裸字跨度>
    • 谢谢你,我从你的文字中学到了。然而,该程序似乎在两个方面被破坏了:1)回想一下斐波那契数是0 1 1 2 3 5 8 ...,而你生成1 1 2 3 5 ..跳过0;您的限制不正确,因为它只是一个简单的计数器。我正在寻找的限制是在值 > 10**15 时突破,因为这是我机器上开始获取 sci 表示法的点。限制是一个修复,但跳过0 并不直观。如果您查看 Python,生成器的统计信息为 a,b=0,1,但这不适用于您的。我认为因为没有yield 与return 不同
    【解决方案2】:

    为了比 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-&gt;next也可以写成$fib-&gt;()。由于生成器还是随机访问的,可以$fib-&gt;reset()或者$fib-&gt;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
    

    【讨论】:

    • 它们作为“数组”“无缝混合”并不完全正确,因为 foreach 仍然无法适应懒惰。您可以覆盖@{},但这仍然需要您立即生成整个列表。
    • @Axeman => 使用类似for (@$fib) { ... } 或for (@{ gen {...} }) {...} 的语法,行为是懒惰的。生成器本身是无限的,但 perl 不会在意,for 循环的迭代器会按顺序将项目拉出生成器。
    • perl -e 'use List::Gen; for (@{;gen {$_**2} 0, 1_000_000_000}) { last if $_ &gt; 100; print "$_\n"}' 返回 10 个值并退出,没有创建任何其他值,也没有任何启动延迟
    • @Eric Strom:所以您只需绑定一个数组,然后将 maxint 发送回 FETCHSIZE?
    • @Eric Strom:你的模块看起来不错。我正在为 Perl 5 编写惰性列表。但我有完美主义倾向,有时我看不到足够好的东西。 Perl 最初只要求数组大小,这让我放弃了 for 循环来处理最初未知大小但元素有限的惰性列表。 last 在这种情况下可以正常工作,但这是我当时无法忍受的额外部分。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      在 Perl 中,如果您没有看到您想要的(在 MANDATORY 访问CPAN FIRST!之后),您可以自己滚动类似于基于 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 页(顺便说一句强烈推荐的书...)

      在这种情况下,annon sub 只返回一个值。我所知道的在 Perl 中可以在标量上下文中工作的唯一循环机制是 while;我认为其他人在继续之前尝试填写列表。因此,如果您在列表上下文中调用 anon sub,它将忠实地返回下一个斐波那契数,这与 Python 的 for 迭代器不同,并且循环将终止。这就是我放置return undef if .... wantarray 的原因,因为它在所写的列表上下文中不起作用。

      有办法解决这个问题。实际上,您可以编写像 map foreach 等一样的子例程,但它不像 Python 的 yield 那样简单。您将需要在 foreach 循环中使用一个附加函数。权衡是 Perl 方法具有巨大的功能和灵活性。

      您可以在 Mark Jason Dominus 的优秀著作“Higher Order Perl”Chapter 4 is all about Interatorsbrian d foy 中阅读有关 Perl 迭代器的更多信息,在 Perl 评论中的 Interators 上也有出色的 article。

      【讨论】:

      • 为什么不在fibo 中计算一次10**$mag 而不是每次调用迭代器时都重复计算?
      • @cjm:你说得对,这样会更好……我明天再编辑。
      • +1。谢谢!这正是产生了我的 Python 脚本的输出 - 我认为更快 - 你的叙述非常有帮助。您能否在foreach 表单中显示如何使用fibo 和iter 的代码?虽然您的代码有效并且文本看起来正确,但在我写这篇文章时您只获得了 2 票。我错过了什么吗?是否有理由认为这不应该是公认的答案?
      【解决方案5】:

      有一个很好的实际示例 here 和一篇 PDF 文章 here...但是我对 Perl 太生疏了,无法尝试直接实现您的挑战(如您所见,示例和方法都在PDF 使用不那么直接的方法)。

      【讨论】:

        【解决方案6】:

        在这种情况下,可以使用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);
        }
        

        【讨论】:

        • 是的,但这是一个递归函数,而不是迭代器。
        • Oesor:问题是如何实现一般的斐波那契数列
        • 我很欣赏介绍记忆。 Oesor 是对的:我知道如何递归地计算斐波那契数。我想知道如何做一个interator
        【解决方案7】:

        这里是根据最初提出的问题量身定制的回答。

        任何实现惰性列表(例如 List::Gen、Memoize 等)并允许您提供自己的生成器子例程(我不是指 Python 中的“生成器”)的 perl 模块都将允许您这样做在这个例子中显示。这里延迟生成列表的模块称为 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 5 8 13 21 34 55 89 144 233 377

        这里使用的惰性列表模块在幕后并没有发生什么神奇的事情。这就是 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;
        }
        

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 2010-10-11
            • 2012-10-17
            • 1970-01-01
            • 2011-01-19
            • 1970-01-01
            • 2020-06-29
            • 1970-01-01
            相关资源
            最近更新 更多