A Prehistoric
Perl & Raku
Fizz-E-Buzz

by Arne Sommer

A Prehistoric Perl & Raku Fizz-E-Buzz

[30] Published 30. August 2019

Perl 6 → Raku

This article has been moved from «perl6.eu» and updated to reflect the language rename in 2019.

This is my long overdue response to the Perl Weekly Challenge #001.

I learned about the Perl Weekly Challenge when the result of the first one was published (and referred to in The Perl 6 Weekly). I responded to the second Challenge, and the rest, as they say, is history...

But I'll have a go at this very first challenge now.

Challenge #1.1

Write a script to replace the character ‘e’ with ‘E’ in the string ‘Perl Weekly Challenge’. Also print the number of times the character ‘e’ is found in the string.

I'll start with a Perl 5 version:

File: eE-5
#! /usr/bin/env perl

use feature say;

my $string = $ARGV[0] || 'Perl Weekly Challenge';  # [1]

my $count = $string =~ tr/e/E/;                    # [2]

say "$string (with $count replacements).";         # [3]

[1] The user can specify the text to transform on the command line. The default is the one given in the challenge.

[2] The «tr» operator (which is short for transliteraton) does the job. And it returns the number of replacements.

[3] Print the new string, and the «e» count.

Running it:

$ perl eE-5
PErl WEEkly ChallEngE (with 5 replacements).

Then the Raku version:

File: eE-wrong
sub MAIN (Str $string is copy = 'Perl Weekly Challenge'); # [1]
{
  my $count = $string ~~ tr/e/E/;

  say "$string (with $count replacements).";
}

[1] I have used the «MAIN» procedure to catch the input, if any. The variable is marked with «is copy», as we change the content (with tr») and procedure parameters are read only by default.

See docs.raku.org/syntax/tr/// for more information about the transliteration operatortr///.

Running it gives a nasty surprise, as «tr» returns the new string:

$ raku eE-wrong
PErl WEEkly ChallEngE (with PErl WEEkly ChallEngE replacements).

We can do the «e» count manually like this:

File: eE-elems
sub MAIN (Str $string is copy = 'Perl Weekly Challenge');
{
  my $count = $string.comb.grep(* eq "e").elems; # [1]

  $string ~~ tr/e/E/;

  say "$string (with $count replacements).";
}

[1] Start with the string, turn it into an array of single characters (with «comb»), sort out (and keep) the elements that are equal to «e» (with «grep»), and count them (with «elems»).

But that is a lot of code. It really would have been nice if «tr» had returned the count, as it does in Perl 5.

The documentation for «tr» (which it is a good idea to look at) says that it «Returns the StrDistance object that measures the distance between original value and the resultant string».

It does indeed measure the distance (whatever is meant by that), but the result should have been a number. But wait; it says «StrDistance object». And the object word is important.

The «StrDistance» name (in the abovementioned documentation) is clickable, and leads to the documentation for that type. This section explains it: «A StrDistance object will stringify to the resulting string after the transformation, and will numify to the distance between the two strings.»

See doc.raku.org/syntax/tr/// for more information about «tr».

See doc.raku.org/type/StrDistance for more information the «StrDistance» class.

So we must ensure that $count is coerced to a number, before the «say» statement stringifies it.

File: eE
sub MAIN (Str $string is copy = 'Perl Weekly Challenge');
{
  my $count = $string ~~ tr/e/E/;

  say "$string (with { +$count } replacements)."; # [1]
}

[1]Prefixing a value with «+» numifies it. The curlies are there to tell the compiler to treat whatever is inside them as code, and place the result back in the string.

See doc.raku.org/routine/+ for more information the «Prefix +» Operator.

Challenge #1.2

Write a one-liner to solve the FizzBuzz problem and print the numbers 1 through 20. However, any number divisible by 3 should be replaced by the word ‘fizz’ and any divisible by 5 by the word ‘buzz’. Those numbers that are both divisible by 3 and 5 become ‘fizzbuzz’.

I didn't know that this was a problem, but here is a multi-line solution:

File: fizzbuzz
for 1 .. 20 -> $curr      # [1]
{
  if $curr %% 5           # [2]
  {
    $curr %% 3 ?? say 'fizzbuzz' !! say 'buzz';  # [3]
  }
  elsif $curr %% 3        # [4]
  {
    say 'fizz';           # [4]
  }
  else
  {
    say $curr;            # [5]
  }
}

[1] Loop through the values.

[2] If the current value is divisible by 5,

[3] • and is divisible by 3, print 'fizzbuzz', else (only divisible by 5) print 'buzz'. This line was written with the one liner in mind.

[4] If not divisible by 5 but divisible by 3, print 'fizz'.

[5] Else (neither divisible by 3 nor 5) print the number itself.

See doc.raku.org/routine/??!! for more information about the «infix ?? !!» operator.

Running it:

$ raku fizzbuzz  
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

As a one-liner (withiout the «{» and «}» characters) and semicolons («;»)):

File: fizzbuzz2
say $_ %% 5 ?? ( $_ %% 3 ?? 'fizzbuzz' !! 'buzz' ) !! ($_ %% 3 ?? 'fizz' !! $_ ) for 1 .. 20;

As a true one-liner, on the command line:

$ raku -e 'say $_ %% 5 ?? ( $_ %% 3 ?? "fizzbuzz" !! "buzz" ) !! ($_ %% 3 ?? "fizz" !! $_ ) for 1 .. 20'

I had to use single quotes on the whole script, to prevent the shell from replacing special characters. That led to double quotes around he output strings.

The Perl 5 one-liner:

$ perl -E 'say $_ % 5 == 0 ? ( $_ % 3 == 0 ? "fizzbuzz" : "buzz" ) : ($_ % 3 == 0? "fizz" : $_ ) for (1..20)'

Perl 5 doesn't have the divisible operator (%%) that Raku has, so we use the modulo operator (%) and compare the result to 0.

We can use «map» and turn it into a sequence, shown here in Raku:

File: fizzbuzz-map
.say for (1..20).map({ $_ %% 5 ?? ( $_ %% 3 ?? 'fizzbuzz' !! 'buzz' ) !! ($_ %% 3 ?? 'fizz' !! $_ ) })

It is a little longer than «fizzbuzz2», and just as hard to understand.

And that's it.