Perl Weekly Challenge Further thoughts
So I've had some thoughts on the Perl Weekly challenge. First up I thought I'd do them in Perl5 as well.
Updating E
The code for this looks very similar to my Perl6 solution (because they aren't that different)
use v5.10;
use strict;
my $s = "Perl Weekly Challenge";
my $c = 0;
$c++ while $s =~ s!e!E!;
say "Updated $s";
say "Number of matches : $c";
All I had to do was tell it to use 5.10 (so I could use say
why this is not on by default now I don't know) and change $s ~~ s!e!E!
to $s ~= s!e!E!
so that was easy enough :)
Fizz Buzz
For FizzBuzz I'll take my Perl6 version and modify it to be Perl5 like. This took a little more work
use v5.10;
use strict;
sub fz { $_[0] % 3 == 0 ? "Fizz" : "" }
sub bz { $_[0] % 5 == 0 ? "Buzz" : "" }
sub fb { my $i = shift; (fz( $i ) . bz( $i ) ) || $i }
say join( "\n", map { fb($_) } (1..20) )
Once again I'm using 5.10 because I like say. Not making operators just simple functions and there's no divisible by operator %%
so I have to fall back to %% 3 == 0
but the core idea is the same.
Further FizzBuzzery
While I was thinking about this I realised that fz
and bz
are basically the same thing. And as has been said to me before repition of code is bad. Now in Perl5 you can make a closure and that's neat but Perl6 gives you some more options.
Lets start with a generic check function (I'm going to drop operators for this next bit and instead play with functional programming).
sub check( Int $n, Str $text, Int $val ) { $val %% $n ?? $text !! "" }
So the fizz function for variable $i
is :
check( 3, "Fizz", $i )
Which is lovely but the 3, "Fizz"
bit looks a bit magic. It'd be nice to encapsulate that in a closure luckily Perl6 gives us
a helpful method on Code blocks to do that. Say hello to assuming
.
my &fz = &check.assuming( 3, "Fizz" );
my &bz = &check.assuming( 5, "Buzz" );
Here we define fz
as being check
assuming you call it with 3
and "Fizz"
for the first two arguments. (And bz
as 5
and "Buzz"
).
Of course you can also use named arguments in assuming
lets create a checker
function that can work with one or more subs that
take 1 input and return a String (that might be blank).
sub checker( Int $i, :@refs ) {
([~] @refs.map( { $_($i) } ) ) || $i.Str;
}
So we take a list of references and then map over the list with our given number, use the reduction metaoperator to concatenate the
results. Using this we can make out fizz-buzz
function :
my &fizz-buzz = &checker.assuming( refs => [&fz, &bz] );
Of course since fz
and bz
are only used here we don't really need to define them before :
my &fizz-buzz = &checker.assuming( refs => [
&check.assuming( 3, "Fizz" ),
&check.assuming( 5, "Buzz" )
] );
And then we can call our function as before :
sub check( Int $n, Str $text, Int $val ) { $val %% $n ?? $text !! "" }
sub checker( Int $i, :@refs ) {
([~] @refs.map( { $_($i) } ) ) || $i.Str;
}
my &fizz-buzz = &checker.assuming( refs => [ &check.assuming( 3, "Fizz" ), &check.assuming( 5, "Buzz" ) ] );
(1..20).map( -> $i { fizz-buzz($i) } ).join("\n").say;
Of course that's a lot of work. But what if later we wanted to play FizzBuzzPingPong where you say "Ping" if it's a prime number and Pong if it's divisible by 2. Well then you can easily make the fizz-buzz-ping-pong
function :
my &fizz-buzz-ping-pong = &checker.assuming( refs => [
&check.assuming( 3, "Fizz" ),
&check.assuming( 5, "Buzz" ),
{ $_.is-prime ?? "Ping" !! "" },
&check.assuming( 2, "Pong" )
] );
Note that for Ping we need to create a new block but that drops into the array easily enough.
Running this :
(1..30).map( -> $i { fizz-buzz-ping-pong($i) } ).join("\n").say;
Gives us :
1
PingPong
FizzPing
Pong
BuzzPing
FizzPong
Ping
Pong
Fizz
BuzzPong
Ping
FizzPong
Ping
Pong
FizzBuzz
Pong
Ping
FizzPong
Ping
BuzzPong
Fizz
Pong
Ping
FizzPong
Buzz
Pong
Fizz
Pong
Ping
FizzBuzzPong
And... I think that'll do for this challenge.
Addendum
It was pointed out to me that the FizzBuzz challenge was supposed to be one line... Oops.
Here you go.
perl6 -e '(1..20).map( { ( [~] ( $_ %% 3 ?? "Fizz" !! "", $_ %% 5 ?? "Buzz" !! "" ) ) || $_.Str } ).join("\n").say'
Back to brute force. With a bite of meta reduction for the fun of it. I still like the functional stuff though.