| Iterator-Simple documentation | Contained in the Iterator-Simple distribution. |
Iterator::Simple - Simple iterator and utilities
use Iterator::Simple;
sub foo {
my $max = shift;
my $i = 0;
iterator {
return if $i > $max;
$i++;
}
}
my $iterator = foo(20); # yields 0,1,2, ..., 19, 20;
$iterator = imap { $_ + 2 } $iterator; # yields 2,3,4,5, ... ,20,21,22
$iterator = igrep { $_ % 2 } $iterator; # yields 3,5,7,9, ... ,17,19,21
# iterable object
$iterator = iter([qw(foo bar baz)]); # iterator from array ref
$iterator = iter(IO::File->new($filename)); # iterator from GLOB
# filters
$iterator = ichain($itr1, $itr2); # chain iterators;
$iterator = izip($itr1, $itr2); # zip iterators;
$iterator = ienumerate $iterator; # add index;
# general filter
$iterator = ifilter $iterator, sub {
return $_ if /^A/;
return;
}
# how to iterate
while(defined($_ = $iterator->())) {
print;
}
while(defined($_ = $iterator->next)) {
print;
}
while(<iterator>) {
print;
}
Iterator::Simple is yet another general-purpose iterator utilities.
Rather simple, but powerful and fast iterator.
Iterator::Simple doesn't export any functions by default. please import them like:
use Iterator::Simple qw(iter list imap);
For all functions:
use Iterator::Simple qw(:all);
Iterator constructor. CODE returns a value on each call, and if it is exhausted, returns undef. Therefore, you cannot yields undefined value as a meaning value. If you want, you could use Iterator module which can do that.
Generally, you can implement iterator as a closure like:
use Iterator::Simple qw(iterator);
sub fibonacci {
my($s1, $s2, $max) = @_;
iterator {
my $rv;
($rv, $s1, $s2) = ($s1, $s2, $s1 + $s2);
return if $rv > $max;
return $rv;
}
}
my $iterator = fiboacci(1, 1, 1000);
You can iterate it in several ways:
while(defined($_ = $iterator->())) {
print "$_\n";
}
next method while(defined($_ = $iterator->next)) {
print "$_\n";
}
while(<$iterator>) {
print "$_\n";
}
If $object is an iterator created by Iterator::Simple, returns true.
False otherwise.
This function auto detects what $object is, and automatically turns it into an iterator. Supported objects are:
__iter__ method. next method. iarray()) iarray()) iter().) (empty iterator.)If it fails to convert, runtime error.
return true if $object can be converted with iter($object)
This fuction converts $object into single array referece.
next method. If it fails to convert, runtime error.
Note that after list($iterator), that iterator is not usable any more.
This is the iterator version of map. Returns an iterator which yields
the value from source iterator modified by CODE.
This is the iterator version of grep. Returns an iterator which yields
the value from source iterator only when CODE returns true value.
When $iterable yields another iterator, iterate it first.
$subitr = iter([10, 11,12]); $source = iter([ 1, 2, $subitr, 4]); $flattened = iflatten $source; # yields 1, 2, 10, 11, 12, 4.
This is the combination of imap, igrep, iflatten. it supports modify (imap) , skip (igrep), and inflate (iflatten). but it should be faster than combination of them.
For example:
$combination = iflatten
imap { $_ eq 'baz' ? iter(['whoa', 'who']) : ":$_:" }
igrep { $_ ne 'bar' }
iter [ 'foo', 'bar', 'baz', 'fiz' ];
$itr = iter [ 'foo', 'bar', 'baz', 'fiz' ];
$filterd = ifilter $itr, sub {
return if $_ eq 'bar'; #skip
retrun iter(['whoa', 'who']) if $_ eq 'baz'; #inflate
return ":$_:"; # modify
};
Both of them will yields ':foo:', 'whoa', 'who', ':fiz:'.
This function returns an iterator which chains one or more iterators. Iterates each iterables in order as is, until each iterables are exhausted.
Example:
$itr1 = iter(['foo', 'bar', 'baz']); $itr2 = iter(['hoge', 'hage']); $chained = ichain($itr1, $itr2); # yields 'foo', 'bar', 'baz', 'hoge', 'hage'.
This function returns an iterator yields like:
$ary = iter(['foo', 'bar', 'baz', ... ]); $iter = ienumerate $ary; # yields [0, 'foo'], [1, 'bar'], [2, 'baz'], ...
Accepts one or more iterables, returns an iterator like:
$animals = iter(['dogs', 'cats', 'pigs']); $says = iter(['bowwow', 'mew', 'oink']); $zipped = izip($animals, $says); # yields ['dogs','bowwow'], ['cats','mew'], ['pigs', 'oink'].
Note that when one of source iterables is exhausted, zipped iterator will be exhausted also.
Same as islice of itertools in Python. If $end is undef or
negative value, it iterates source until it is exhausted.
$step defaults to 1. 0 or negative step value is prohibited.
$iter = iter([0,1,2,3,4,5,6,7,8,9,10,11,12]); $sliced = islice($iter, 3, 13, 2); # yields 3, 5, 7, 9, 11.
islice($iterable, 0, $count, 1);
islice($iterable, $count, undef, 1);
Turns array reference into an iterator. Used in iter($arrayref).
You do not have to use this function directly, because
iter($arrayref) is sufficient.
Iterator used in Iterator::Simple is just a code reference blessed in Iterator::Simple::Iterator. This class implements several method and overloads some operators.
Just bless $coderef in Iterator::Simple::Iterator and returns it.
Call undelying code.
Returns self. You don't need to use this.
Overloading '<>' makes this possible like:
print while <$iterator>;
$iterator | $coderef1 | $coderef2;
is equivalent to:
$iterator->filter($coderef1)->filter($coderef2);
is equivalent to:
ifilter(ifilter($iterator, $coderef1), $coderef2);
For example, $iterator->flatten() is equivalent to
iflatten $iterator.
All iterator transformation function calls iter function on all source
iterables. So you can pass just array reference, GLOB ref, etc.
These examples completely do the right thing:
imap { $_ + 2 } [1, 2, 3, ... ];
ienumerate(\*STDIN);
# DBIx::Class::ResultSet has 'next' method.
ifilter $dbic_resultset, sub {CODE};
You can implement __iter__ method on your objects in your application.
By doing that, your object will be Iterator::Simple friendly :).
Note that __iter__ method must return an iterator.
There is another iterator module in CPAN, named Iterator and Iterator::Util made by Eric J. Roode that is great solution. Why yet another iterator module? The answer is *Speed*. You use iterator because you have too many data to manipulate in memory, therefore iterator could be called thousands of times, speed is important.
For this simple example:
use Iterator::Util qw(iarray imap igrep);
for(1 .. 100) {
my $itr = igrep { $_ % 2 } imap { $_ + 2 } iarray([1 .. 1000]);
my @result;
while($itr->isnt_exhausted) {
push @result, $itr->value;
}
}
meanwhile:
use Iterator::Simple qw(iarray imap igrep);
for(1 .. 100) {
my $itr = igrep { $_ % 2 } imap { $_ + 2 } iarray([1 .. 1000]);
my @result;
while(defined($_ = $itr->())) {
push @result, $_;
}
}
Iterator::Simple is about ten times faster!
That is natural because Iterator::Simple iterator is just a code reference, while Iterator.pm iterator is full featured class instance. But Iterator::Simple is sufficient for usual demands.
One of most downside of Iterator::Simple is, you cannot yields undef value as a meaning value, because Iterator::Simple thinks it as a exhausted sign. If you need to do that, you have to yield something which represents undef value.
Also, Iterator::Simple cannot determine iterator is exhausted until next iteration, while Iterator.pm has 'is(nt)_exhausted' method which is useful in some situation.
Rintaro Ishizaki <rintaro@cpan.org>
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Iterator-Simple documentation | Contained in the Iterator-Simple distribution. |
package Iterator::Simple; use strict; use Carp; use UNIVERSAL qw(isa); use Scalar::Util qw(blessed reftype); use overload; use base qw(Exporter); use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS); use constant ITERATOR_CLASS => 'Iterator::Simple::Iterator'; $VERSION = '0.05'; $EXPORT_TAGS{basic} = [qw(iterator iter list is_iterator)]; $EXPORT_TAGS{utils} = [qw( ifilter iflatten ichain izip ienumerate islice ihead iskip imap igrep iarray is_iterable is_listable )]; push @EXPORT_OK, @{$EXPORT_TAGS{basic}}, @{$EXPORT_TAGS{utils}}; $EXPORT_TAGS{all} = [@EXPORT_OK]; sub iterator(&) { ITERATOR_CLASS->new($_[0]);} # name: iter # synopsis: iter($object); # description: # autodetect object type and turn it into iterator # param: object: object to turn into iterator # return: iterator sub iter { if(not @_) { return iterator { return }; } my($self) = @_; if(blessed $self) { if($self->isa(ITERATOR_CLASS)) { return $self; } my $method; if($method = $self->can('__iter__')) { return $method->($self); } if($method = overload::Method($self, '<>') || $self->can('next')) { return ITERATOR_CLASS->new(sub { $method->($self) }); } if($method = overload::Method($self, '&{}')) { return ITERATOR_CLASS->new($method->($self)); } if($method = overload::Method($self,'@{}')) { return iarray($method->($self)); } } if(ref($self) eq 'ARRAY') { return iarray($self); } if(ref($self) eq 'CODE') { return ITERATOR_CLASS->new($self); } if(reftype($self) eq 'GLOB') { return ITERATOR_CLASS->new(sub { scalar <$self> }); } croak sprintf "'%s' object is not iterable", (ref($self)||'SCALAR'); } # name: is_iterable # synopsis: iter($object); # description: # returns given object is iterable or not. # param: object # return: iterator sub is_iterable { my($self) = @_; return not not ( (blessed($self) and ( $self->isa(ITERATOR_CLASS) or $self->can('__iter__') or $self->can('next') or overload::Method($self, '<>') or overload::Method($self, '&{}') or overload::Method($self,'@{}') )) or ref($self) eq 'ARRAY' or ref($self) eq 'CODE' or reftype($self) eq 'GLOB' ); } # name: is_iterator # synopsis: is_iterator($object); # description: # reports Iterator::Simpler iterator object or not; # param: object: some object; # return: bool sub is_iterator { blessed($_[0]) and $_[0]->isa(ITERATOR_CLASS); } # name: list # synopsis: list($object) # description: # autodetect object type and turn it into array reference # param: object: object to turn into array # return: array reference sub list { if(not @_) { return []; } my($self) = @_; if(ref($self) eq 'ARRAY') { return $self; } if(reftype($self) eq 'GLOB') { return [<$self>]; } if(blessed $self) { if($self->isa(ITERATOR_CLASS)) { my(@list, $val); push @list, $val while defined($val = $self->()); return \@list; } my $method; if($method = overload::Method($self,'@{}')) { return $method->($self); } if($method = $self->can('__iter__')) { my(@list, $val); my $iter = $method->($self); push @list, $val while defined($val = $iter->()); return \@list; } if($method = overload::Method($self, '<>') || $self->can('next')) { my(@list, $val); push @list, $val while defined($val = $method->($self)); return \@list; } } croak sprintf "'%s' object could not be converted to array ref", (ref($self)||'SCALAR'); } # name: ifilter # synopsis: ifilter $iterable, sub { CODE }; # description: # filters another iterable object. # if filter code yields another iterator, iterate it until it # exhausted. if filter code yields undefined value, ignores it. # param: source: source iterable object # param: code: transformation code # return: transformed iterator sub ifilter { my($src, $code) = @_; $src = iter($src); if(ref($code) ne 'CODE' and ! overload::Method($code, '&{}')) { croak 'Second argument to ifilter must be callable.'; } my $buf; ref($src)->new(sub { my $rv; if($buf) { return $rv if defined($rv = $buf->()); undef $buf; } while(defined(local $_ = $src->())) { next unless defined($rv = $code->()); return $rv unless isa $rv, ITERATOR_CLASS; $buf = $rv; return $rv if defined($rv = $buf->()); undef $buf; } return; }); } # name: imap # synopsis: imap { CODE } $iterable; # description: # simplified version of ifilter, no skip, no inflate. # param: code: transformation code; # param: source: source iterable object # return: transformed iterator; sub imap(&$) { my($code, $src) = @_; $src = iter($src); ref($src)->new(sub { local $_ = $src->(); return if not defined $_; return $code->(); }); } # name: igrep # synopsis: igrep { CODE } $iterable; # description: # iterator filter iterator # param: code: filter condition # param: source: source iterable object # return: filtered iterator sub igrep(&$) { my($code, $src) = @_; $src = iter($src); ref($src)->new(sub { while(defined(my $rv = $src->())) { local $_ = $rv; return $rv if $code->(); } return; }); } # name: iflatten # synopsys: iflatten $iterable; # description: # if source iterator yields another iterator, iterate it first. # param: source: source iterable object # return: flatten iterator sub iflatten { my($src) = @_; $src = iter($src); my $buf; ref($src)->new(sub { my $rv; if($buf) { return $rv if defined($rv = $buf->()); undef $buf; } while(1){ $rv = $src->(); return if not defined $rv; return $rv unless isa $rv, ITERATOR_CLASS; $buf = $rv; return $rv if defined($rv = $buf->()); undef $buf; } }); } # name: ichain # synopsis: ichain($iterable1, $iterable2,...) # description: # iterate one or more iterater one by one. # param: iteraters: one or more iterable object # return: chained iterator sub ichain { my @srcs = map { iter($_) } @_; if(@srcs == 1) { return $srcs[0]; } ref($srcs[0])->new(sub{ while(@srcs) { my $rv = $srcs[0]->(); return $rv if defined $rv; shift @srcs; } return; }); } # name: ienumerate # sysopsis: ienumerate($iterable); # description: # returns an iterator which yields $souce value with its index. # param: iterable: source iterator # return: iterator sub ienumerate { my($src) = @_; $src = iter($src); my $idx = 0; ref($src)->new(sub{ my $rv = $src->(); return if not defined $rv; return [$idx++, $rv]; }); } # name: izip # synopsis: izip($iterable, ...) # description: # this function returns an iterator yields array reference, # where i-th array contains i-th element from each of the argument iterables. # param: iterables: list of iterables; # return: zipped iterator; sub izip { my @srcs = map { iter($_) } @_; ref($srcs[0])->new(sub{ my @rv; for my $src (@srcs) { my $rv = $src->(); return if not defined $rv; push @rv, $rv; } return \@rv; }); } # name: islice # synopsis: isplice($iterable, $start, $end, $step); # description: # this function returns an iterator, # param: iterable: source iterable object # param: start: how many first values are skipped # param: end: last index of source iterator # param: step: step # return: sliced iterator sub islice { my($src, $next, $end, $step) = @_; $next = defined $next ? int($next) : 0; $end = defined $end ? int($end) : -1; $step = defined $step ? int($step) : 1; if($next == $end) { $next = -1; } $src = iter($src); my $idx = 0; ref($src)->new(sub{ return if $next < 0; my $rv; while($rv = $src->()) { if($idx++ == $next) { $next += $step; if($end > 0 and $next >= $end) { $next = -1; } return $rv; } } return; }); } sub ihead {islice($_[1], 0, $_[0]);} sub iskip {islice($_[1], $_[0]);} # name: iarray # synopsis: iarray $array_ref; # description: # creates iterator from array reference # param: array_ref: source array reference # return: iterator sub iarray { my($ary) = @_; if(ref($ary) ne 'ARRAY') { croak 'Argument to iarray must be ARRAY reference'; } my $idx = 0; iterator { return if $idx == @$ary; return $ary->[$idx++]; }; } # class Iterator::Simple::Iterator is underlying Iterator object. # It is just a blessed subroutine reference. { package Iterator::Simple::Iterator; use Carp; use overload ( '<>' => 'next', '|' => 'filter', fallback => 1, ); sub new { if(ref($_[1]) ne 'CODE') { croak 'Parameter to iterator constructor must be code reference.'; } bless $_[1], $_[0]; } sub next { goto shift } sub __iter__ { $_[0] } *filter = \&Iterator::Simple::ifilter; *flatten = \&Iterator::Simple::iflatten; *chain = \&Iterator::Simple::ichain; *zip = \&Iterator::Simple::izip; *enumerate = \&Iterator::Simple::ienumerate; *slice = \&Iterator::Simple::islice; sub head { Iterator::Simple::ihead($_[1], $_[0]); } sub skip { Iterator::Simple::iskip($_[1], $_[0]); } } 1; __END__