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.
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.
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.
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.