3

I love the functional-programming paradigm that List::Gen brings to Perl. Writing a Collatz sequence with it should be doable, albeit a little challenging since the length of the list is not known a priori.

I'm missing the final 1 at the end of the sequence with the following code:

use List::Gen '*';
iterate{ $_%2 ? 3*$_+1 : $_/2 }->from( 23 )->while( '!=1' )->say;

which prints:

23 70 35 106 53 160 80 40 20 10 5 16 8 4 2

What I essentially need with this approach is a do-while. The documentation makes mention of a while_, which is a 'look-ahead' version of while, but the interpreter cannot find such a method.

Zaid
  • 36,680
  • 16
  • 86
  • 155

2 Answers2

1

This works (as a start):

use List::Gen '*';
iterate{$_%2 ? 3*$_+1 : $_/2}->from(23)->until(sub{$_ == 1 ? (($delay = 1), 0) : $delay})->say();

Let me see if I can make a function out of that and make $delay safe...

This should work, but doesn't because the function passed to until is called twice (except for the first value):

use List::Gen '*';
sub after { use feature 'state'; $f = shift(); $f = '$_' . $f unless (ref($f)); sub { state $d; $r = $d; $d = eval $f; $r } }
iterate{ $_%2 ? 3*$_+1 : $_/2 }->from( 23 )->until(after('==1'))->say;

This works for the double function call:

use List::Gen '*';
sub after { use feature 'state'; $f = shift(); $f = '$_' . $f unless (ref($f)); sub { state($d1,$d2); $r = $d2; $d2 = $d1; $d1 = eval $f; $r } }
iterate{ $_%2 ? 3*$_+1 : $_/2 }->from( 23 )->until(after('==1'))->say;

Still trying to understand why the until function is called twice after the first call.

It works only for until and not while.

The code above only works with string arguments; this one works with function references:

#!/usr/bin/perl
use strict;
use List::Gen '*';

sub after {
        use feature 'state';
        my $f = shift();
        my $c = ref($f) eq 'CODE'
                        ? '&$f()'
                        : '$_' . $f;
        sub {
                state($d1,$d2);
                my $r = $d2;
                $d2 = $d1;
                $d1 = eval($c);
                $f;
                $r
        }
}
iterate{$_%2 ? 3*$_+1 : $_/2}->from(23)->until(after('==1'))->say;
iterate{$_%2 ? 3*$_+1 : $_/2}->from(23)->until(after(sub{$_ == 1}))->say;
kjpires
  • 730
  • 4
  • 14
  • Hmm... Had a nice function, but found that after the first `while` or `until` call, subsequent `while` or `until` calls are executed twice for the same value: i.e. 23, 70, 70, 35, 35, 106, 106, ..., 4, 4, 2, 2, 1! See: `iterate{ $_%2 ? 3*$_+1 : $_/2 }->from( 23 )->while(sub{print "_ = $_\n"; $_ != 1})->say;` – kjpires Jun 04 '15 at 22:10
  • I figured out a [workaround](http://stackoverflow.com/a/30664416/133939) that feels cleaner which involves appending an `undef` after the first `1` is encountered and testing for `defined`-ness – Zaid Jun 05 '15 at 10:37
  • If it wasn't for the double call (which still has me perplexed), I could have make this delay function return `N` additional sequences which might have been useful for non-terminating sequences. – kjpires Jun 05 '15 at 10:53
0

Here is a workaround that tests the element for defined-ness to decide when to end the list. It requires modifying the iterator definition to populate an undef element immediately after it encounters a 1 in the chain:

iterate{ $_ == 1 ? undef : $_%2  ? 3*$_+1 : $_/2 }->from( 23 )->while( 'defined' )->say;

which prints

23 70 35 106 53 160 80 40 20 10 5 16 8 4 2 1
Zaid
  • 36,680
  • 16
  • 86
  • 155