Что такое Perl-версия итератора Python? - PullRequest
39 голосов
/ 23 сентября 2010

Я изучаю Perl на своей работе и наслаждаюсь им.Я обычно делаю свою работу на Python, но босс хочет Perl.

Большинство понятий в Python и Perl хорошо совпадают: Python dictionary = Perl hash;Кортеж Python = список Perl;Список Python = массив Perl;и т. д.

Вопрос: существует ли Perl-версия формы Python для Iterator / Generator?

Пример. Классический способ 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-форма Iterator - это форма, к которой я привык, и я не нахожу ее документированной в 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 ]

36 голосов
/ 23 сентября 2010

Концепция итератора в 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 );
34 голосов
/ 23 сентября 2010

Для еще более гибкого решения, чем генераторы 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
16 голосов
/ 23 сентября 2010

Отличная книга Perl высшего порядка (доступна бесплатно по указанной ссылке) содержит много информации по смежным темам, в частности, содержит целую главу об итераторах.Под «высшим порядком» автор подразумевает использование способностей Perl в качестве функционального языка с первоклассными функциями для реализации всевозможных интересных вещей.Это действительно очень хорошая книга - я прочитал большую ее часть, и главы об итераторах и потоках потрясающие.Я настоятельно рекомендую хотя бы просмотреть его, если вы планируете писать код на Perl.

8 голосов
/ 23 сентября 2010

Существует аналогичный метод для создания Iterator / Generator, но он не является "гражданином первого класса", как на Python.

В Perl, если вы не видите, что хотите (после ОБЯЗАТЕЛЬНО поездка в CPAN FIRST !), вы можете свернуть свой собственный, который похож на итератор Python, основанный на замыканиях Perl и анонимной подпрограмме.

Рассмотрим:

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 , которое позволяет поддерживать постоянные переменные.Вы можете сделать то же самое, имея модуль, похожий на C / C ++.Внутри fibo анонимная подпрограмма выполняет работу по возврату следующего элемента данных.

Цитата из Библии Perl"Вы будете несчастны, пока не узнаете разницу между скалярным и списочным контекстом" - стр. 69 (настоятельно рекомендуется книга между прочим ...)

В этом случае сабвуфер annon возвращает только одно значение.Единственный известный мне в Perl механизм зацикливания, который может работать в скалярном контексте, это while;Другие пытаются заполнить список, прежде чем продолжить, я думаю.Поэтому, если вы вызвали подпункт anon в контексте списка, он должным образом вернет следующее число Фибоначчи, в отличие от Python для итераторов, и цикл завершится.Вот почему я поставил return undef if .... wantarray, потому что он не работает в контексте списка, как написано.

Есть способы исправить это.В самом деле, вы можете написать подпрограммы, которые действуют как map foreach и т. Д., Но это не так просто, как выход Python.Вам понадобится дополнительная функция для использования внутри цикла foreach.Компромисс в том, что подход Perl обладает огромной мощью и гибкостью.

Подробнее об итераторах Perl вы можете прочитать в превосходной книге Марка Джейсона Доминуса "Perl высшего порядка" Глава 4 посвящена интеграторам Брайан Д. Фой также имеет отличную статью об Интеграторах в Perl Review.

6 голосов
/ 23 сентября 2010

Есть хороший практический пример здесь и PDF-статья здесь ... но я слишком устарел в Perl, чтобы попытаться реализовать вашу задачу напрямую (как вы увидите, и пример, и подход в PDF используют менее прямой подход).

4 голосов
/ 25 июня 2011

Вот ответ, специально разработанный для точного соответствия поставленному вопросу.

Любой модуль perl, который реализует отложенные списки (например, List :: Gen, Memoize и т. Д.), А также позволяет вам предоставить собственную подпрограмму генератора (я не имею в виду «генератор», как в 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 0 1 1 2 3 5 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;
}
3 голосов
/ 22 октября 2013

В 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;
}  
3 голосов
/ 26 сентября 2010

В этом случае можно использовать памятку.

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