Quick Tip #2: Fancier sequences

Every day of the Learning Perl 6 Kickstarter campaign, I’ll present a quick tip about something I like in Perl 6. Yesterday I posted User-defined infinite sequences. In that post I only showed sequences of positive numbers. That’s a bit boring. Let’s make something fancier.

Let’s start with one of the boring examples that every programming language likes to use. It’s a bit not-safe-for-work. About 800 years ago, Fibonacci spent a lot of time thinking about what adult rabbits do with their free time. He didn’t invent the problem, but he gets all the credit:

my $fibonacci := 1, 1, { $^a + $^b } ... *;
say "Fibonacci: " ~ $fibonacci[0..10];

Here’s the result:

Fibonacci: 1 1 2 3 5 8 13 21 34 55 89

We can create an infinite, lazy sequence. (As a side not, doesn’t that sound wrong? The order of adjectives in English is customarily quantity followed by opinion. Doesn’t lazy, infinite lists sound better?)

But, what’s going on with that sequence? I start off with two literals, the 1 and 1. But, after that I have a block. When Perl 6 sees a block in the sequence, it uses it to compute the next element. And, I can do anything I like in that block.

But, what’s going in the block? There’s a $^a and a $^b. Perl 6 figures out that this block takes two arguments and grabs the previous two items from the sequence to fill those variables. The last evaluated expression in the block becomes the next value in the sequence. After that it starts all over. And, since I used the Whatever * at the end, the sequence goes on forever.

The Lucas Sequence is similar:

my $lucas := 2, 1, 3, { $^a + $^b } ... *;
say "Lucas: " ~ $lucas[0..10];

Here’s the result:

Lucas: 2 1 3 4 7 11 18 29 47 76 123

Let’s do something more interesting. There’s a sequence of “digit product”. You take the number and split it up into individual decimal digits. Multiply all of those and add that product to the original number. That’s the next number in the sequence. Here it is:

my $d := 1, { $^a + [*] $^a.comb } ... *;
say "Digit product: " ~ $d[0..10];

Here’s the result:

Digit product: 1 2 4 8 16 22 26 38 62 74 102

There are a few interesting things in that block. First, I call the comb method to break the number up into digits. In front of that is the [*] reduction operator. It takes two elements from the succeeding list, performs the operation inside the square braces, and puts the result back on the list. It does this until there’s a single element left. That gives me the product of the digits.

Once I have the product, I add it to the original number (still in $^a). Now I have the next item in the sequence.

Not fancy enough? How abou the “speak and say” sequence? Take a number, such as 1. Now, say the count of the like digits and the digit word. So, there is “one 1”. Translate that into digits, so 11. Do it again. There’s “two 1s”, or 21, then, “one 2 and one 1”, so 1211. Keep doing that until you get bored with it:

my $speak_and_say := 1, { .subst( /(<[0..9]>)$0*/, { .chars() ~ .[0] }, :g ) } ... *;
say "Speak and say: " ~ $speak_and_say[0..5];

The result is:

Speak and say: 1 11 21 1211 111221 312211

4 comments

  1. Not necessarily golfed, more idiomatic perhaps.

    my @fib = 1, 1, * + * … *;
    my @lucas = 2, 1, * + * … *;
    my @dprod = 1, { $^a + [*] $a.comb } … *;
    my @look-and-say = 1, *.subst(/(.)$0*/, { .chars ~ .[0] }, :g) … *;

    put “Fib: “, @fib[^11];
    put “Lucas: “, @lucas[^11];
    put “Digit product: “, @dprod[^11];
    put “Look and say: “, @look-and-say[^11];

  2. The easy way to split a string into characters is with the .comb method.
    Hence:

    my $d := 1, { $^a + [*] $^a.comb } … *;

    The .subst method passes the Match object of each match
    as the argument to the specified replacement block.
    So you just need:

    my $speak_and_say := 1, { $^a.subst( /(\d)$0*/, {.chars() ~ .[0]}, :g) } … *;

    Though I’d be inclined to write that in a way that is a little punctuated and a little more self-explanatory:

    my $speak_and_say := 1, { .subst( /<digit>$<digit>*/, {.chars() ~ .}, :g) } … *;

    (Note the use of unary dot instead of $^a in the sequence block as well).

    1. > my $speak_and_say := 1, { .subst( /$*/, {.chars() ~ .}, :g) } … *;

      I cannot compile it on Rakudo version 2020.01 built on MoarVM version 2020.01.1.
      The error mesage is:
      “Malformed postfix call (only alphabetic methods may be detached)
      Damian, would you please debug the code? Thanks a lot.

  3. > There’s a subst-mutate that doesn’t change the original but returns a modified versio

    my $speak_and_say := 1, { S:g/(()$0*)/{$0.chars() ~ $/[0][0]}/ given $^a } … *

Leave a Reply to khoguan Cancel reply

Your email address will not be published. Required fields are marked *