Syntactic Confectionery Delight PerlMonks

### Re: Let's get lazy

by runrig (Abbot)
 on Nov 02, 2001 at 04:31 UTC ( #122728=note: print w/replies, xml ) Need Help??

in reply to Let's get lazy

tilly once posted something that could be coerced to do this.

Update: It would be kind of the inside-out solution to this problem, you could get a function callback for every combination of values, but I think to get an actual iterator function using this method, similar to the iterator produced by my \$iter = do { my \$i; sub { \$i++ } };, you'd need a co-routine. Unless tilly can wrangle it out of his code :-)

There does seem to be a Coroutine Module on CPAN, might be fun to look into :)

Update: Taking the initiative, I wrangled tilly's code myself (just couldn't wait for the book :)

```
use strict;
use warnings;

my \$iterator = mk_iter(
[1..2], ["a".."c"], [3..5]
);

while (my @arr = \$iterator->()) {
print "@arr\n";
}

sub mk_iter {
my \$range = shift;
my \$i = 0;
my \$end = @\$range;
my \$iter = sub {
return unless \$i < \$end;
return \$\$range[\$i++];
};
@_ ? ret_iter(\$iter, @_) : \$iter;
}

sub ret_iter {
my \$iter = shift;
my \$range = shift;
my \$i = 0;
my \$end = @\$range;
my @arr;
my \$new_iter = sub {
\$i = 0 unless \$i < \$end;
return unless \$i or @arr = \$iter->();
return @arr, \$\$range[\$i++];
};
@_ ? ret_iter(\$new_iter, @_) : \$new_iter;
}

#####################################
# Update
# Here's a variation which is closer to what was
# Originally asked for, i.e. all combinations from
# 2-4 characters
use strict;
use warnings;

use strict;
use warnings;

my \$iterator = make_iter(
2,4,[qw(A C G N T)]
);

while (my @arr = \$iterator->()) {
print "@arr\n";
}

sub make_iter {
my (\$start, \$end, \$range) = @_;
my \$nxt_iter = sub { return };
my \$iter = sub {
my @data;
unless (@data = \$nxt_iter->()) {
return unless \$start <= \$end;
\$nxt_iter = mk_iter( (\$range) x \$start++);
return \$nxt_iter->();
};
@data;
}
}

Replies are listed 'Best First'.
Laziness in a more consistent way
by tilly (Archbishop) on Nov 03, 2001 at 02:50 UTC
Here is a more general solution to the problem. In this solution my iterators all indicate that they are done by returning an empty list, and when called next will restart. I didn't use i_grep, but I included it to show how you would do it.

Note that despite the length, the code is more straightforward than the original recursive code. And if this was part of a longer program, you would be reusing the bulk of this code.

```use strict;

my \$iter = i_map(
sub {print "@_\n"},
comb_iter(
list_iter(1..2), list_iter('a'..'c'), list_iter(3..5)
)
);

1 while \$iter->();

###################################################################
# The program proper ends here.  These are utility functions that #
# you could reuse                                                 #
###################################################################

# Takes a list of iterators that are "restartable"
# Returns a restartable iterator that iterates over all combinations
# of outputs of the input iterators, creating a flat list of combinati
+ons
# of the inputs.  (The output only makes sense in array context.)
sub comb_iter {
if (0 == @_) {
return sub {()}; # Stupid case needed for generality.
}
elsif (1 == @_) {
return shift;
}
else {
my \$outer_iter = shift;
my \$inner_iter = comb_iter(@_);
my @last_outer;
return sub {
if (@last_outer) {
my @ret = \$inner_iter->();
if (@ret) {
return (@last_outer, @ret);
}
else {
@last_outer = \$outer_iter->();
if (@last_outer) {
return (@last_outer, \$inner_iter->());
}
else {
return ();
}
}
}
else {
@last_outer = \$outer_iter->();
return (@last_outer, \$inner_iter->());
}
};
}
}

# Takes a function and an iterator, returns an iterator that uses that
# function to filter the output.
sub i_grep {
my (\$filter, \$iter) = @_;
my @last_ret = qw(just an initialization value);
sub {
while (@last_ret) {
@last_ret = \$iter->();
if (\$filter->(@last_ret)) {
return wantarray ? @last_ret : \$last_ret[0];
}
}
return ();
};
}

# Takes a function and an iterator, returns an iterator that applies t
+hat
# function to the returns of the iterator.
sub i_map {
my (\$filter, \$iter) = @_;
sub {
my @ret = \$iter->();
return @ret ? \$filter->(@ret) : ();
};
}

# Takes a list and turns it into an iterator over that list
sub list_iter {
my @vals = @_;
my \$i = 0;
sub {
if (\$i < @vals) {
return \$vals[\$i++];
}
else {
\$i = 0;
return ();
}
};
}
Note that the specific problem in the original question can now be solved as the author wanted using i_grep, or you can produce more efficient iterator as follows:
```my \$genome_iter = i_map(
sub {join '', @_},
join_iter(
map {
comb_iter(
map {
list_iter(qw(a c g n t));
} 1..\$_
)
} 2..3
)
);

while (my \$string = \$genome_iter->()) {
print "\$string\n";
}

# Takes a list of iterators, and returns an iterator that iterates
# over each in turn
sub join_iter {
my @iter = @_;
my \$i = 0;
return sub {
while (\$i < @iter) {
my @ret = \$iter[\$i]->();
if (@ret) {
return wantarray ? @ret : \$ret[0];
}
else {
\$i++;
}
}
\$i = 0;
return ();
};
}
Alternately if you want to turn the output into a list you can just create an easy method:
```# Takes an iterator and returns a list of results
sub iter2list {
my \$iter = shift;
my @out;
while (my @ret = \$iter->()) {
push @out, @ret;
}
return @out;
}
Note that most of the length here is because I am having to build my iterator interface from scratch. That is a lot of work! And some of the code looks more complex because what we are used to seeing in a few nested loops our minds balk at when you see it as a similar number of nested calls.
Re (tilly) 2: Let's get lazy
by tilly (Archbishop) on Nov 02, 2001 at 17:07 UTC
A much better person to ask is Dominus, he is in the process of writing a book on exactly this subject.

Chapter 4 in particular offers direct iterative solutions of the above problem. Once you know the techniques, they are straightforward to apply in any language with proper support for closures. And the techniques are essentially to create utility functions that take one iterator and create new ones out of it. For instance if you like doing stuff procedurally with map, grep, an easy way to produce a range etc, then you can create iterative versions of the same. (For instance a range would return all of the things in that range. An iterative map would take a function and an iterator and give you an iterator that is the result of applying that function to the output of the first iterator.) And then it becomes a mechanical process to write iterative versions of what you can dream up in a list-oriented manner.

Alternately if you want the flavour of a co-routine solution to the problem, and don't want to wait for Perl 6, Ruby offers them now. So, I believe, does the very latest version of Python. (Ruby goes further and people there use them more often.) They don't offer the rest of Perl 6, but they give you the flavour of a Perlish scripting language with yield and (with Ruby or stackless Python) full continuations.

Create A New User
Node Status?
node history
Node Type: note [id://122728]
help
Chatterbox?
 [LanX]: ... well like always :) [zentara]: LanX One day, I will see a great interest in Tk. It's small, self-contained, needs very few external libraries, AND makes it easy to make high-contrast displays. :-) [zentara]: Corion in this cased they left the www. out [zentara]: /d// :-) [LanX]: i like tk, just joking. but what about modern styles? LanX has to run. .. o/ [zentara]: modern styles are secondary to display readability. Tk is automatically good at the clunky industrial theme. :-)

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (8)
As of 2017-05-26 12:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
My favorite model of computation is ...

Results (189 votes). Check out past polls.