🎄 2/25. Grepping dividable numbers in Perl 6

🎄 2/25. Grepping dividable numbers in Raku

N. B. Perl 6 has been renamed to Raku. Click to read more.


Welcome to Day 2 of the Perl 6 One-Liner Advent Calendar! Today, we’ll solve a nice task from Project Euler with number 1. Once again, let me warn you that the rest of the text contains a solution, so you are welcome to make a pause to think of your own solution first. But I am almost sure that if you are reading perl6.online then you most likely solved the problems from Project Euler in the past.

The task is to find the sum of all multiples of 3 and 5 below 1000. The first elements of the row of our interest are 3, 5, 9, 15, 20, 21, etc. You can already see that some of them, such as 15, are multiples of both 3 and 5, so it is not possible to add up multiples of 3 and 5 separately.

The short story is here:

say sum((1..999).grep: * %% (3 | 5))

Now let us decipher it.

What we need is to filter the numbers that are multiples of either 3 or 5. If you re-read the previous sentence, a bell should ring for you: in Perl 6 this can be achieved with junctions, informally known as quantum superpositions. To test if a number is dividable by either 3 or 5, write the following:

$x %% 3 | 5

By the way, %%, the divisibility operator, is a very sweet thing that helps avoiding negations in Boolean tests, which you would have written as follows, would you only have a single percent:

!($x % (3 | 5))

OK, the main condition is ready, let us scan the numbers between 1 and (including) 999:

(1..999).grep: * %% (3 | 5)

A few more interesting Perl 6 elements appear here. For example, the WhateverCode block, which was introduced by a * character (learn more about it in my article All the stars of Perl 6 published in the previous Perl 6 Advent Calendar). Together with the colon-form of method calls, it allows to get rid of a nested pair of braces and parentheses:

(1..999).grep({$_ %% (3 | 5)})

The numbers are filtered (grepped if you prefer), and it’s time to add them up and print the result. We come to the final one-liner shown at the beginning of this post.

Of course, instead of writing say sum(...) one could do a method call or two:

((1..999).grep: * %% (3 | 5)).sum.say

As a bonus track, here’s my first solution:

sub f($n) {
    ($n <<*>> (1...1000 / $n)).grep: * < 1000
}

say (f(3) ∪ f(5)).keys.sum;

And that’s the end of today’s story! Come again tomorrow!