Zed_Lopez has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to use Algorithm::Loops::NestedLoops to dynamically create a set of loops (of depth to be determined at runtime) like this:

for $i (0..1) { for $j ($i+1..3) { for $k ($j+1..5) { for $l ($k+1..7) {

and so on. NestedLoops makes the $i+1 part easy, but I don't know how to generate the anonymous subroutines such that they'll have the right values for the ends of the ranges.

The obvious approach

my $depth = 4; my $loop = [[0..1]]; my $val = 3; for my $i (1..$depth-1) { push @$loop, sub {[$_+1..$val]}; $val+=2; } my $iter = NestedLoops($loop); while (my @list = $iter->()) { output(@list); } sub output { my @args = @_; print join '', @args, "\n"; }

fails because $val isn't evaluated when the subroutine is defined; it's evaluated when it's run, such that they all get its last value, 9.

Trying to be more clever by using an iterator:

my $n = shift; sub plustwo_iter { my $val = 1; return sub { $val +=2; return $val; } } my $p = plustwo_iter(); my $loop = [[0..1], (sub {[$_+1..&$p]}) x ($n-2)]; my $iter = NestedLoops($loop); while (my @list = $iter->()) { output(@list); }

fails because NestedLoops seems to end up calling the iterator more than exactly once per loop (not that I'd want to depend on the assumption it did call it exactly once per loop.)

Does anyone see how this can be done with NestedLoops? (I have another solution where I build code and eval it, but I'd like to try it with NestedLoops, too.)

Replies are listed 'Best First'.
Re: Building the loop arrayref for Algorithm::Loops::NestedLoops
by ikegami (Patriarch) on Sep 02, 2004 at 05:37 UTC

    yay closures!

    use Algorithm::Loops qw( NestedLoops ); sub create_iterator_closure { my $p = $_[0]*2-1; return sub { [ $_+1..$p ] }; } sub output { my @args = @_; local $, = ", "; local $\ = "\n"; print(@args); return 1; } { my $depth = 4; my @iters; push(@iters, [ 0..1 ]); push(@iters, create_iterator_closure($_)) foreach (2..$depth); NestedLoops(\@iters, \&output); } __END__ Output ====== 0, 1, 2, 3 0, 1, 2, 4 0, 1, 2, 5 0, 1, 2, 6 0, 1, 2, 7 0, 1, 3, 4 0, 1, 3, 5 0, 1, 3, 6 0, 1, 3, 7 0, 1, 4, 5 0, 1, 4, 6 0, 1, 4, 7 0, 1, 5, 6 0, 1, 5, 7 0, 2, 3, 4 0, 2, 3, 5 0, 2, 3, 6 0, 2, 3, 7 0, 2, 4, 5 0, 2, 4, 6 0, 2, 4, 7 0, 2, 5, 6 0, 2, 5, 7 0, 3, 4, 5 0, 3, 4, 6 0, 3, 4, 7 0, 3, 5, 6 0, 3, 5, 7 1, 2, 3, 4 1, 2, 3, 5 1, 2, 3, 6 1, 2, 3, 7 1, 2, 4, 5 1, 2, 4, 6 1, 2, 4, 7 1, 2, 5, 6 1, 2, 5, 7 1, 3, 4, 5 1, 3, 4, 6 1, 3, 4, 7 1, 3, 5, 6 1, 3, 5, 7

      Yeah, that works. Your closure is cleverer than my closure! Thanks, ikegami.

Re: Building the loop arrayref for Algorithm::Loops::NestedLoops (stays shared)
by tye (Sage) on Sep 02, 2004 at 15:13 UTC

    The smallest fix is to simply move the 'my' to be inside the loop:

    for my $i (1..$depth-1) { my $val = 1 + 2*$i; push @$loop, sub {[$_+1..$val]}; }

    or, even

    my $val= 3; for my $i ( 1 .. $depth-1 ) { my $end= $val; push @$loop, sub {[$_+1..$end]}; $val += 2; }

    and the reason this makes a difference is that your code uses a single instance of the $val variable and all of the closures you make share it. When the closures finally get run [several times each, during the call to NestedLoops()] is when the value of $val is used. $val doesn't stay 3 so your loops don't stop at 3.

    You can build the list in-line, for example:

    #!/usr/bin/perl -w use strict; use Algorithm::Loops 'NestedLoops'; my $depth= 4; my $iter= NestedLoops( [ [0..1], map { my $end= 2*$_+1; sub { [$_+1..$end] } } 1..$depth-1, ], ); while( my @list= $iter->() ) { output(@list); } sub output { print join '', @_, "\n"; }

    - tye