Perl 6 small stuff #15: Long story about short answers to Perl Weekly Challenge no. 1

Last week I discovered a new Twitter account, the Perl Weekly Challenge. This is an initiative by Mohammad Anwar where Perl 5 and 6 programmer will get a programming challenge every week. There will be challenges both for beginners and advanced programmers.

This week we got the first two-part challenge. The first — the beginner challenge — was to substitute every ‘e’ in the string “Perl Weekly Challenge” and count every occurence of ‘e’. The second — the expert challenge — was to program a one-liner that solves the Fizz Buzz challenge for every integer from 1 through 20. But if the integer is divisible with 3 or 5 or both, the integer should be replaced with Fizz, Buzz or FizzBuzz respectively.

Let’s start with the latter. If we omit the one-liner requirement, an easy and relatively obvious solution would look something like these two:

// JavaScript/Node solution using if statements
for (i = 0; i < 20; i++) {
if (i % 3 == 0 && i % 5 == 0) console.log("Fizz Buzz");
else if (i % 3 == 0) console.log("Fizz");
else if (i % 5 == 0) console.log("Buzz");
else console.log(i);
}
# A more Perl 6 variant with "switch/case" syntax
for 1..20 {
given $_ {
when $_ %% 3 && $_ %% 5 {
say "Fizz Buzz":
}
when $_ %% 3 {
say "Fizz";
}
when $_ %% 5 {
say "Buzz";
}
default {
say $_;
}
}
}

Now — one could always convert the Perl 6 code into a one-liner by removing the line feeds and adding ; after the }’s. But that would neither look nor be very elegant.

What I wanted was a solution that almost was — and almost read — like one single statement. What I came up with was this:

say gather { take "Fizz " if $_ %% 3; take "Buzz" if $_ %% 5 } || $_ for 1..20

If I may say so myself this code use gather in a rather clever way. Checking for fizz and buzz is no longer an either/or scenario, but an “and” scenario. gather/take will generate a one or two element list if the requirements are met; either (Fizz), (Buzz) or (Fizz Buzz) if both criteria are met. If none of the criteria are met, the || (“or”) selects and returns the integer instead.

The resulting output looks like this:

1
2
(Fizz )
4
(Buzz)
(Fizz)
7
8
(Fizz)
(Buzz)
11
(Fizz)
13
14
(Fizz Buzz)
16
17
(Fizz)
19
(Buzz)

Should you want to remove the parentheses from the output, add a join:

say join " ", gather { take "Fizz" if $_ %% 3; take "Buzz" if $_ %% 5; } || $_ for 1..20

But I wanted the code to be as short as possible — without join it is 9 characters shorter. The shorter version is the one I submitted.


Now, the beginner challenge was also an interesting one. Count the number of e’s in the string “Perl Weekly Challenge” and also replace them with capitalized e’s.

There are several ways to do this. The obvious way (perhaps) is this:

my $text = "Perl Weekly Challenge";
$text ~~ s:g/e/E/;
say $text;
say "E's: " ~ $text.comb('e').elems;

.comb filters out characters/elements matching a criteria — in my case ‘e’ — and returns them as a list. The elems routine returns the number of elements in the list.

But there had to be a more concise and perhaps elegant way to do this. Enter S: Capital S is a substitution operator that unlike its miniscule sibling — s — doesn’t change the string it’s used on, but returns a new string with changes instead. That means that you can do substitutions on immutable strings. Note that the syntax is a little different:

my $t = "test 1-2-3";
my $t2 = S:g/\d/NUMBER/ given $t;
say $t; # unchanged
say $t2;
# Output:
test 1-2-3
test NUMBER-NUMBER-NUMBER

That the S doesn’t attempt to change the original gives us the possibility to run it on immutable strings as well:

say S:g/i/I/ given "original";
# Output: 
orIgInal

I combined this with the possibility to run code within strings using the {} brackets. I placed an S substitution there so that…

say "{S:g/e/E/ given "Perl Weekly Challenge"}";
# Output:
PErl WEEkly ChallEngE

Now I’ve reduced the substitution and the printing to one line. To keep the final code in one line, I had to figure out a way to count the number of e’s without using a comb method. I figured I could use Perl 6’s built-in $/ array for that instead. This array contains the match objects created by the latest regex. Counting how many e’s there are is now just a matter of using the array method .elems which returns the number of objects.

The end result was this:

say "{S:g/e/E/ given "Perl Weekly Challenge"}, E={$/.elems}";
# Output:
PErl WEEkly ChallEngE, E=5

Note that the brackets are executed in the order they appear from left to right. That means that $/.elems will not return what you expect if you put it at the start of the string.

Anyway — the resulting code was short and clear, and I was quite satisfied with both this and the FizzBuzz code. Thanks to The Perl Weekly Challenge no. 1 I forced myself to think a little differently that I normally do, and employed techniques I hadn’t really employed before. A great learning experience.


POSTSCRIPT — the Perl 5 solutions
One of the best things of teaching myself Perl 6 is that it has rekindled my love for and interest in Perl 5. I now constantly discover that modern Perl 5 has evolved a lot since the Perl 5 learnt to know 20 years ago. Many of the things I like about Perl 6 is now available in Perl 5 — although with a different syntax.

Non-destructive substitutions is available in Perl 5 too, so the Perl 5 answer to the substitution exercise is not that different from Perl 6:

print "Perl Weekly Challenge" =~ s/e(?{$c++})/E/gr . ", E=$c\n";
# Output:
PErl WEEkly ChallEngE, E=5

The trick is the /r modifier turns on the non-destructive feature. This is one of the “new” things in Perl that I didn’t know about (I’m constantly reminded the amount of stuff I don’t know about Perl 5 outweighs what I know by a good margin).

What Perl 5 does not have, however, is a way to easily track how many substitutions are made. Enter the (?{}) part of the regex, a way to execute code within a regex. Here I just increment a variable $c, and print it after the substitutions are done. A little different from the Perl 6 version, but just as compact.

As for the FizzBuzz challenge the answer is quite different, but concise like its Perl 6 cousin:

print map { "$_\n" } ($_, qw{Fizz Buzz}, "Fizz Buzz")[!($_ % 3) + !($_ % 5) * 2 ] for (1..20);
# Output:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizz Buzz
16
17
Fizz
19
Buzz

For each integer 1..20 I create an ad hoc array consisting of the integer, Fizz, Buzz and Fizz Buzz. Which of the elements will be chosen is determined by the result of testing the integer mod 3 or integer mod 5. Success leaves zero as it should do; if unsucessful it returns the remainder.

Knowing this we can use the fact that Perl 5 doesn’t really have an understanding of true and false in the traditional way but rather assumes any 0 value to be false and any non-zero value to be true. So I flip the results using the ! operator: If int % 3 returns zero, it becomes true — or 1. The same goes for int % 5. I multiply the latter with 2, and add the results of the two test. The result is zero if neither of the mods returns 0; 1 if % 3 returns zero, 2 if % 5 returns zero and 3 if both % 3 and % 5 returns zero.

As it happens these numbers correspond to elements in the ad hoc array, so that 0 returns the integer itself, 1 Fizz, 2 Buzz and 3 Fizz Buzz.

In total the code returns a 20-element array with 1..20 or their Fizz Buzz replacements. Finally, for this to be printable I run it trough map and add a newline to the elements. In short: The Perl 5 version of the code is totally different from the Perl 6 version, but equally concise. I’m happy with both.

In any case — this has been so fun that I look forward to next week’s challenge.


NOTE
Compared with the original article, I made a few small adjustments both to the Perl 6 and 5 versions of the code, so that the Fizz Buzz matches are printed “Fizz Buzz” and not “FizzBuzz”. I didn’t know about that criteria before I read Dave Cross’s post about his solutions to these challenges. The additional code becomes marginally longer, but not significantly.