CodeEval Fizz Buzz

Do you know the Fizz Buzz game? We take a couple of number, say 2 and 3, call the first one Fizz and the second Buzz. Then we say the numbers as they are in the natural series, uttering "Fizz!" instead of the first number (2 in my case) and all its multiples, and Buzz for the second and multiples. If a number is a multiples of both of them I should say "FizzBuzz!".

The reason to talk about it here is that Fizz Buzz is the first Codeeval problem, and here I am going to present my Python 3 solution to it.

As usual, I started writing a few test cases. In this case I merely converted the two examples provided by CodeEval in Python Unit Tests:
class TestFizzBuzz(unittest.TestCase):

    def test_provided_1(self):
        result = solution('3 5 10 ')
        self.assertEqual('1 2 F 4 B F 7 8 F B ', result)

    def test_provided_2(self):
        result = solution('2 7 15 ')
        self.assertEqual('1 F 3 F 5 F B F 9 F 11 F 13 FB 15 ', result)
When Fizz is 3, Buzz is 5, and we want to go through the first 10 numbers, I should get what showed in the first test case.
A bit more interesting the second one, where 14, should generate a FizzBuzz result.

Being myself a C/C++ programmer at heart, I came out with a first solution in line with my background:
def solution(line): #1
    result = [] #2
    x, y, n = map(int, line.split()) #3
    for i in range(1, n + 1): # 4
        fb = False # 5
        if i % x == 0: # 6
            fb = True
        if i % y == 0:
            fb = True
        if not fb: # 7
        result.append(' ') # 8

    return ''.join(result) # 9
1. The parameter line is supposed to be a character string.
2. I would push each result in a list of strings, and in the end I will convert the list to a single string. This is done for efficiency reasons, being a Python string immutable.
3. Extract the values passed by the user. Notice that no error handling is done here, we boldly assumes we have three blank-separated meaningful numbers in it. For the rules of CodeEval this is our expected way of programming. Don't do that at work. Besides, notice also that I have used the Python built-in map function to convert the strings generated by split() to integers.
4. Loop on the natural series, starting from one up to n, as required by the caller. Since Python use the right-open interval concept, I had to increase n by one to get the expected behavior.
5. The problem has a tiny complication. We need to keep track if a number is either multiple of Fizz or Buzz. To achieve this result I use a boolean variable that I named fb.
6. If the current number is a Fizz (or a Buzz) push its letter to the result list.
7. If it is not a Fizz/Buzz push its actual value to the list. But be careful, we want it as a string, so we have to explicitly convert it to that type.
8. A single blank is added as separator.
9. Finally, we convert the list to a string, by joining it.

This code works fine. However anyone could see that it has been written by a C/C++ programmer. By the way, after writing it, I checked in the blog and I found out I had already written a post on this CodeEval problem, proposing a C++ solution. As you can see, the code looks like a transcription of the same concepts with minimal variations.

I decided to think to a more pythonesque solution, and I came out with this:
def solution(line):
    x, y, n = map(int, line.split())
    result = [
        (((i % x == 0) * 'F' + (i % y == 0) * 'B') or '%d' % i) + ' '
        for i in range(1, n + 1)
    return ' '.join(result)
Basically, is the same thing. However, I moved the for loop inside the list initialization, and I used the clever trick of multiplying a letter for a boolean, knowing that False means zero and True one. Then using the or operator to check if any Fizz/Buzz has been detected and, if not, pushing in the list the number, converted to string.

On GitHub you can find the full Python 3 source code for the unit test and the actual solutions.

No comments:

Post a Comment