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.

Perl Weekly Challenge 1

Tags:

Recently the wonderfully talented Mohammad S Anwar started a new project the Perl Weekly Challenge. As a lover of all (or at least many) things Perl and something of a Perl6 fanatic I figured I would sign up and try to challenge each week using Perl6.

If you've entered the challenge and don't want your result spolied do not read further.

For week one we've got two challenges :

  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’ found in the string.
  2. Write one-liner to solve FizzBuzz problem and print number 1-20. However, any number divisible by 3 should be replaced by the word fizz and any divisible by 5 by the word buzz. Numbers divisible by both become fizz buzz.

E challenge

So the obvious thought on this one is to use regular expressions and I initially whipped up a simple one liner :

perl6 -e 'my $s = "Perl Weekly Challenge";"Number of e {($s ~~ m:g/e/).elems.say}";$s ~~ s:g/e/E/;"Updated {$s.say}"'

Here I make use of the fact that if you do a global match you get a list of all the matches and .elems gives you the count. Then I just do a global replace.

Of course I'm having to match twice against the string. Hmmm. After a little thought I realised there's a different way of doing it that's a little neater. This time I'll write it out in a bit more detail.

my $s = "Perl Weekly Challenge";
my $c = 0;
$c++ while $s ~~ s!e!E!;
say "Updated $s";
say "Number of matches : $c";

So here we make use of the fact that without the :g adverb a replace only does one match at a time. Then we simply increment a counter for each time. Note in this case I fall back to one of my standard quoting options ! which I find works quite well when doing web development where / has a tendency to pop up all over the place.

If I can think of a wackier way to do this in Perl6 I will but a simple replace with counter seems the way I'd generally do it.

FizzBuzz

Ah FizzBuzz, if you ever get asked this in an interview try not to answer with "Really?". For those of you who don't know the main thing to remember about FizzBuzz is in the name. Some numbers are divisible by both 3 and 5 and in this case you need to output FizzBuzz. The leading way to fail this test is not take that into account. Again my first attempt to resolve the challenge relied on my patented skills of brute force and ignorance. I sent it in to Mohammad as a one liner but here it is tided up a bit.

sub prefix:<fb> (Int $i) {
    $i %% 15 ?? "FizzBuzz"
             !! $i %% 5 ?? "Buzz"
                        !! $i %% 3 ?? "Fizz"
                                   !! $i
}

(1..20).map( fb * ).join("\n").say

The indentation here is to mainly show how seriously brute force this method is. Firstly I test for the 3 and 5 case (using the %% divisibility operator) then if that fails try 5, then 3 and finally return the number if there's no other matches. Note that I put this into a fb operator that you can use as a prefix to any Integer EG fb 10 (which would return 10). Currently the operator returns a String or an Integer, it would probably be best to make it always return a String.

Anyway once I've got the operator applying it to each number in 1 to 20 is easy enough. Map the operator (using a Whatever Star code block) against each value and then join the results with a newline and output them.

Still the fb operator is a bit... clunky. Can I streamline things?

sub prefix:<fz> (Int $i) { $i %% 3 ?? "Fizz" !! "" }
sub prefix:<bz> (Int $i) { $i %% 5 ?? "Buzz" !! "" }
sub prefix:<fb> (Int $i) { (fz $i ~ bz $i) || $i.Str }

(1..20).map( fb * ).join("\n").say

Continuing with the operator theme (which fankly I probably shouldn't adding cutom operators to your code tends to slow down parse time.... but I like them) I add two new ones fz and bz these return either Fizz, Buzz or a blank string as required. By concatenating the results of fz $i and bz $i I either have a string (Fizz, Buzz or sometimes FizzBuzz) or a blank string. I can then make use of the short circuit ability of the || or operator. If the left hand side evaluates to true (which the blank string won't) then the || will be that side. Otherwise it's $i.Str (now fb always returns a String). Note that I don't need to use return as the last thing evaluated in a block is it's value.

I've got some thoughts on further updates but I quite like this one.