Подтвердить что ты не робот

Что такое Perl-версия итератора Python?

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

Большинство концепций в Python и Perl отлично сочетаются: Python dictionary = Perl hash; Python tuple = список Perl; Список Python = массив 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 быть динамическим или основываться только на статическом списке?

Форма Iterator на Python - это форма, к которой я привык, и я не нахожу ее документированной в Perl... Помимо написания этого в цикле или рекурсивно или создания огромного статического списка, как мне (для ex) написать подпрограмму Fibonacci в 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"; }

Заранее благодарим вас за новичка...

4b9b3361

Ответ 1

Для еще более гибкого решения, чем генераторы 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;

Сообщите мне, если у вас есть какие-либо вопросы.

Update:

Я выпустил новую версию модуля (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

Концепция итератора немного отличается в 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 );

Ответ 3

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

Ответ 4

Существует аналогичный метод для создания 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 потому что он не работает в контексте списка, как написано.

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

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

Ответ 5

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

Ответ 6

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

Любой модуль 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

Ничего волшебного не происходит за кулисами с использованием модуля ленивого списка. Это то, на что похож Алеф подпрограмма.

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

В этом случае можно использовать 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

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