I’m not sure that I’ll have time to do these every week, but here are my answers to this week’s two Perl Weekly Challenges.
Challenge #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.
1 2 3 4 5 6 7 8 9 10 |
use strict; use warnings; use feature ‘say’; $_ = ‘Perl Weekly Challenge’; my $count = tr/e/E/; say; say “$count changes”; |
Nothing really complicated here. We can discuss why I reached for tr/.../.../ rather than s/.../.../. I just think it’s silly to invoke the full power of the regex engine when you’re only changing a few letters.
Challenge #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.
1 |
perl –E‘say +(fizz)[$_ % 3] . (buzz)[$_ % 5] || $_ for 1 .. 20’ |
This is a bit more interesting. And I have to say that I think this is deeply dirty code. Over the twenty plus years I’ve been writing Perl I’ve trained myself to write code that is use strict and use warnings clean. This code is very much not that and with warnings turned on, you’d see a lot of warnings!
So how does it work?
The core is the (string)[index] construct. We all know that you can access an individual element in an array with code like $my_array[$index]. But you can also do this with a list of values. For example:
1 |
(‘foo’, ‘bar’, ‘baz’)[1] |
returns “bar”. I often use this when I only need some of the values returned by localtime().
1 |
my ($d, $m, $y) = (localtime)[3, 4, 5]; |
Here, I’m using something very similar. (fizz)[$_ % 3] is looking up a value in a list that contains only a single value (the string “fizz”). So if the index is zero then we get the first (and only) value in the list. If the index is anything other than zero then we’re looking for an element that’s off the end of the list and Perl gives us undef, which is interpreted as an empty string. And the index expression, $_ % 3, gives us zero if $_ is exactly divisable by 3. There are two things here that would throw warnings under use strict and use warnings. Firstly, “fizz” is a bareword and secondly, we’re using undef in a concatenation.
We then repeat the same logic using “buzz” and 5 and we concatenate together the results of those two expressions. That gives us either “fizz”, “buzz”, “fizzbuzz” or the empty string. Of those values, only the empty string is seen as false, so we can use || $_ to replace that with the original number.
There’s one mystery left. Why have I put that + before the first expression? I think I’m going to leave that as an exercise for the reader. If you work it out, please leave a comment.
And there we have it. A solution that is simultaneously both horribly dirty and yet, I think, rather clever.
Have you come up with your own solution yet?
Update:
It’s been pointed out to me that my solution for challenge #2 prints “fizzbuzz” for 15, when the specification clearly asks for “fizz buzz” (with a space). So here’s a version which fixes that:
1 |
perl –E‘@a=((fizz)[$_ % 3]//(),(buzz)[$_ % 5]//()), say @a?”@a”:$_ for 1 .. 20’ |
Leave a Reply