The Three Ghosts of FizzBuzz: A Christmas Story

The Three Ghosts of FizzBuzz: A Christmas Story


FizzBuzz is a simple kids game, often used as a test at programming interviews. It goes like this:

"Write a program that prints the numbers from 1 to 100. But for multiples of 3 print “Fizz” instead of the number and for multiples of 5 print “Buzz”. For numbers which are multiples of both 3 and 5 print “FizzBuzz”.

I fell asleep yesterday while thinking about all the different ways we can solve FizzBuzz in Ruby. Then three ghosts appeared in my dream. The first one was the Ghost of Imperative Programming (GImP, for short). It showed me how to do FizzBuzz in an imperative way:

def imperative(n)
  if (n % 3 == 0) && (n % 5 != 0)
    'Fizz'
  elsif (n % 3 != 0) && (n % 5 == 0)
    'Buzz'
  elsif (n % 3 == 0) && (n % 5 == 0)
    'FizzBuzz'
  else n.to_s
  end
end

"Simple conditionals are easy to read", it said. "They're also relatively fast. They're a good, basic way to implement your solution"

Next, the Ghost of Antecedent Declarative Inference appeared (GhAnDI). "Don't listen to GImP" it said. "Be more clever. Look here, the conditions that are evaluated to produce each FizzBuzz value are mutually exclusive, if you represent them as Hash values then only a maximum of one of our Hash keys will have the value of 'true' for any given number. Use this to your advantage."

def declarative(n)
  h ||= {
    'Fizz' => (n % 3 == 0) && (n % 5 != 0),
    'Buzz' => (n % 3 != 0) && (n % 5 == 0),
    'FizzBuzz' => (n % 3 == 0) && (n % 5 == 0),
    n.to_s => (n % 3 != 0) && (n % 5 != 0)
  }
  h.key(true) || n
end

"You then make sure you return this 'true' key (h.key(true)) from the method. Of course if all the keys have the 'false' value (i.e. the passed number is neither divisible by 3, nor by 5) then h.key(true) will evaluate to nil, so use the logical OR operator to ensure that -in this case- you return the actual passed number."

"That's pretty smart" I said. "We are not specifying a sequence of steps leading to the solution, but rather the conditions required and we leverage the language features to work out the correct one. Cool!"

As GhAnDI departed, I sensed a new presence arriving. It was wearing trendy clothes , had a well-oiled beard and there was a large and vocal group of developers following it around. It was... the Ghost of Functional Erudition (GoFEr).

"The other two are the past", GoFEr said. "I will show you the future"

def functional(n)
  [[15, 'FizzBuzz'], [5, 'Buzz'], [3, 'Fizz']].select { |x| (n % x[0] == 0)}.map{|a| a[1] }.first || n.to_s
end

"We specify both conditions and outcome together" it said. "Ruby doesn't have tuples, but we can get a similar effect with an Array of Arrays. We then select the 'tuple' which satisfies the condition of zero-mod division with the tuple's divisor (x[0]). If more than one tuples match, we only select the first one, since we cleverly ordered our Array that way. And if there are no matches then we return the original number input. Simples."

And with that GoFEr shimmied away to the sounds of 'California dreaming' and I woke up. I learned some useful techniques from this encounter, but mostly I came to appreciate just how powerful and flexible the Ruby language really is.

Merry Christmas everyone and happy Ruby-ing!

PS: As a Christmas present, the Ghosts put all their code along with some bench-marking on GitLab (gitlab.com/snippets/35596)