Perl 5 - Итератор - PullRequest
       8

Perl 5 - Итератор

5 голосов
/ 28 января 2011

Я реализовал простой итератор в Perl. Я обычно работаю с C # и довольно часто использую итераторы и функциональное программирование. Поэтому я подумал, что было бы легко получить некоторые основы, работающие в Perl.

Проблема в том, что у меня плохая производительность, я не ожидаю, что будет быстрее, чем для или foreach , но я подумал, что кто-то может дать мне некоторое представление о чтобы ускорить его.

Вот смелость моей посылки:

package Iterator;
use strict;

#Constructor for Iterator type
sub new {
  my $type = $_[0];
  my $this = {};

  #set default values
  $this->{Array} = @_[1];
  $this->{Index} = 0;
 $this->{Sub} = sub { 
   my @array = @{$this->{Array}};
   return $#array >= $this->{Index} ? $array[$this->{Index}++] : undef;
  };

  #return self
  bless($this, $type);
  return $this;
}

#Iterates next
sub Next {
 return $_[0]->{Sub}->();
}

Позволяет вам сделать это:

my $iterator = Iterator->new(\@array);
while (defined(my $current = $iterator->Next())) {
  print $current, "\n";
}

Не бросается в глаза ... пока.

Также включает некоторый функциональный код, подобный этому:

my $sum = 0;
Iterator
  ->new(\@array)
  ->Where(sub { $_[0] % 2 == 0 })
  ->ForEach(sub { $sum += $_[0] });

Что суммирует все четные значения массива.

Моим узким местом является код итерации:

$this->{Sub} = sub { 
   my @array = @{$this->{Array}};
   return $#array >= $this->{Index} ? $array[$this->{Index}++] : undef;
  };

Какие-нибудь указатели, чтобы ускорить это?

Ответы [ 9 ]

5 голосов
/ 28 января 2011

Гораздо более эффективная версия:

package Iterator;
use strict;

#Constructor for Iterator type
sub new {
  my $type = shift;
  my $array = shift;
  my $this = {};
  $this->{array} = $array;
  $this->{index} = 0;
  bless($this, $type);
  return $this;
}

#Iterates next
sub Next {
  my $this = shift;
  return $this->{array}->[$this->{index}++];
}
5 голосов
/ 28 января 2011

Немного опоздал с игрой здесь, но, поскольку вы беспокоитесь о производительности, одно из самых больших узких мест в коде типа итератора заключается в том, что поля вашего объекта, основанного на хэше, должны быть разыменованы при каждом доступе.Одним из способов борьбы с этим является использование замыканий, в которых поля закрываются над переменными, избегая ненужных разыменований.

В моем модуле List :: Gen , который содержит довольно производительную реализацию отложенных списковЯ написал служебную функцию curse, которая заставляет объекты на основе замыкания вести себя как обычные объекты Perl.

Вот краткий пример вашего класса итератора, написанного с помощью curse.В простом тесте, суммирующем 1000 чисел, этот метод в раза вдвое быстрее вашего, даже после устранения всех недостатков, отмеченных в других ответах.

{package Iterator;
    use List::Gen 'curse';
    sub new {
        my ($class, $array) = @_;
        my $index = 0;
        curse {
            next  => sub {$$array[$index++]},
            index => sub :lvalue {$index},
        } => $class
    }
    sub reset {shift->index = 0}
} 

Если вы действительно настаиваетедля большей скорости, поскольку метод next не нужно ничего передавать, вы можете даже написать:

my $next = $iterator->can('next');

while (defined (my $x = $next->()) {...}

, который даст вам повышение скорости на 30-40% по сравнению с обычным вызовом метода.

Вы можете прочитать источник List::Gen для более расширенного использования curse

5 голосов
/ 28 января 2011

эта строка:

my @array = @{$this->{Array}};

дублирует массив в @array, и я не думаю, что вы хотите. Просто выполните $#{$this->{Array}}, чтобы найти конечную точку вашего массива.

5 голосов
/ 28 января 2011

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

4 голосов
/ 28 января 2011

Посмотрите на List :: Util и List :: MoreUtils для утилит, которые могут помочь вам в этом.Вы даже можете использовать perl5i для более современного вида синтаксиса.

Пример:

use perl5i::2;

my @nums = (0..100);
my $sumEven = @nums->grep(sub { $_ % 2 == 0 })->reduce(sub { $a+$b });

say $sumEven;
4 голосов
/ 28 января 2011

Суммирование четных чисел проще сделать с помощью grep и List::Util:

use List::Util 'sum';
say sum grep { not $_ % 2 } (1 .. 10); // 30

Мне кажется очень вероятным, что код, предложенный в вашем вопросе, слишком сложен. Если вы не можете придумать достойный пример, который не может быть легко решен с использованием традиционных примитивов Perl.

2 голосов
/ 28 января 2011

Не выгружать сохраненный массив.Вы копируете каждый элемент массива из того места, на которое он указывает $this->{Array}, в локальный список @array, когда делаете это:

my @array = @{$this->{Array}};

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

$this->{Sub} = sub { return $this->{Array}[++$this->{Index}]; }

Это все, что вам нужно.Когда {Index} выходит за пределы диапазона, он возвращает undef.

Кроме того, вы можете написать свое выражение на Perl как:

$sum += $_ foreach grep { $_ % 2 == 0 } @array;
2 голосов
/ 28 января 2011

В CPAN уже есть массив итераторов , поэтому вы можете посмотреть на его подход, если вы еще этого не сделали.

Кстати, в вашем коде у вас есть:

#set default values
$this->{Array} = @_[1];

Полагаю, вы хотите сказать $_[1]. С @_[1] вы запрашиваете фрагмент массива одного элемента. В конце результат тот же, но семантика - нет. Любопытно, что я ожидал иметь массив из одного элемента, если я сделаю @_ [1] или ошибку, но протестирован в отладчике, и вы получите скаляр (по крайней мере, в perl 5.10). В любом случае Perl 6 пойдет по этому пути и не изменит сигил для доступа к элементам в массивах или хэшах, поэтому вы кодируете «продвинутый» Perl; -)

2 голосов
/ 28 января 2011

Гораздо более простой Perl-итератор:

my @array = (1, 2, 3, 4);
while (my $i = shift @array)
{
    print $i . "\n";
}
...