Perl Weekly Challenge Further thoughts

Tags:

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.