I am learning Perl at my work and enjoying it. I usually do my work in Python but boss wants Perl.
Most of the concepts in Python and Perl match nicely: Python dictionary=Perl hash; Python tuple=Perl list; Python list=Perl array; etc.
Question: Is there a Perl version of the Python form of an Iterator / Generator?
An example: A Classic Python way to generate the Fibonacci numbers is:
#!/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
Iterators are also useful if you want to generate a subsection of a much larger list as needed. Perl 'lists' seem more static - more like a Python tuple. In Perl, can foreach
be dynamic or is only based on a static list?
The Python form of Iterator is a form that I have gotten used to, and I do not find it documented in Perl... Other than writing this in loops or recursively or generating a huge static list, how do I (for ex) write the Fibonacci subroutine it in Perl? Is there a Perl yield
that I am missing?
Specifically -- how do I write this:
#!/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"; }
Thanks in advance to being kind to the newbie...
An iterator is an object that contains a countable number of values. An iterator is an object that can be iterated upon, meaning that you can traverse through all the values. Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__() .
The next() function returns the next item in an iterator. You can add a default return value, to return if the iterable has reached to its end.
An iterator object implements __next__, which is expected to return the next element of the iterable object that returned it, and to raise a StopIteration exception when no more elements are available. In the simplest case, the iterable will implement __next__ itself and return self in __iter__.
Here is a list of the differences between Iterable and Iterator in Python. An Iterable is basically an object that any user can iterate over. An Iterator is also an object that helps a user in iterating over another object (that is iterable). We can generate an iterator when we pass the object to the iter() method.
The concept of an iterator is a little different in Perl. You basically want to return a one-use subroutine "closed" over the persistent variables.
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++; }
And if you don't like the while
loop, then here is two shots at some syntactic sugar, which basically accomplish an each-item loop.:
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 );
For an even more flexible solution than Python's generators, I have written the module List::Gen on CPAN which provides random access lazy generator arrays:
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
Since generators pretend to be arrays, they can mix seamlessly with normal perl code. There is also an object oriented approach:
my $fib; $fib = cache gen {$_ < 2 ? $_ : $fib->get($_ - 1) + $fib->get($_ - 2)}; say join ' ' => $fib->slice(0 .. 15);
In each case, the generator is lazy, calculating nothing upon creation, and then calculating only those values required to satisfy the slices. The recursive definition of the Fibonacci sequence calls itself many times, so the cache
function is used to make sure each value is only calculated once.
You can also use generators as iterators:
while (my $num = $fib->next) { last if $num > 10**15; print "$_\n"; }
$fib->next
can also be written $fib->()
. Since the generator is still random access, you can $fib->reset()
or $fib->index = 10;
Let me know if you have any questions.
Update:
I have released a new version of the module (0.80) that makes it easier to use iterative algorithms in generators. Here is an example that closely mirrors the OP's example:
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};
if you use bigint;
before or at the top of the sub, you can of course:
say for @{fibonacci 400}; # or more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With