FizzBuzz Challenge in Ruby – Thinking Functionally

A while ago, one of my coworkers suggested a fun exercise on a Friday afternoon: Solve the (in)famous FizzBuzz interview question…without using any conditional statements.

It's a good "thinking outside the box" spin on the exercise, a little brain teaser for devs of all skill levels. There are several ways to do it, but today we're going to show how simple it can be when you have some functional thinking in your arsenal. Of course functional programming has conditionals, but understanding both imperative and declarative strategies can help you solve many problems and overcome restrictions.

Transforming Control Flow to Expressions

Starting with the basic requirements of FizzBuzz, we can break the behavior down into 3 "conditionals":

  1. If the number is divisible by 3, we always want to print "fizz"
  2. If the number is divisible by 5, we always want to print "buzz" (which may or may not be appended to a "fizz")
  3. If the number is divisible by neither 3 nor 5, print the number itself

The first step in getting rid of conditionals is to think about how we can transform the control flow into statements. Instead of thinking:

  • "if true, print 'fizz', otherwise don't print at all"

we can think:

  • "if the number is divisible by 3, print 'fizz' 1 time, otherwise print 'fizz' 0 times".

Now we've moved the behavior from imperative control flow to a mathematical expression: print 'fizz' * x, where x is 0 or 1 depending on the input number. To find this x, we will need to transform our input number somehow. Let's call our input n, and this transformation function f, so that f(n) = x. Now, given some input n, we can replace #1 above with print 'fizz' * f(n).

A quick note about multiplying a string by a number: Ruby makes it trivial, as string-integer multiplication is in the standard library.

str * integer → new_str
Copy — Returns a new String containing integer copies of the receiver. integer must be greater than or equal to 0.

Using this transformation for all three conditions, we can come up with three statements to replace the three conditionals:

print 'fizz' * f(n)

print 'buzz' * g(n)

print n * h(n)

where f(n), g(n), and h(n) each returns 0 or 1, depending on if n is divisible by 3, 5, or neither.

Creating Our Functions

Our task is now to find functions f, g, and h, which seem an awful lot like they will require similar techniques. A natural first step is to apply the modulo function n % x, which will give us 0 if n is evenly divisible by x, or a positive number otherwise. If we can then just transform whatever positive number we get into 1, we have our two condition cases. (Yes, in this case we are getting 0 when we want to multiply by 1, and vice versa, but we'll fix that later).

Essentially, we are looking for a function like this:

f(x) = {
   0 if x == 0
   1 if x > 0
}

Dividing a number by itself would get you 1 or 0, except for the fact that instead of 0, it's actually indeterminate (and dividing by 0 will crash your code). How can we try to avoid this 0/0 situation? Well, the real problem is the 0 on the bottom, and we've already constrained our input to be non-negative…so what happens if we just add 1 to the denominator?

  • 0/1 = 0
  • 1/2 = 0.5
  • 2/3 = 0.667
  • 3/4 = 0.75
  • 49/50 = 0.98
  • 99/100 = 0.99
  • If you don't believe me here, do some calculus

It turns out that if our input is 0, we get 0, and if it's any positive number, we get something between 0.5 and 1. One simple application of the ceil function, and we've just transformed our output to exactly 0 or 1!

In Ruby, this looks like:

(n % 3 / (n % 3 + 1.0)).ceil

Note that you need to add 1.0 and not 1, so that Ruby returns a Float.

This will give us 0 when our input is divisible by 3, and 1 otherwise, which happens to be…exactly the opposite of what we want. To flip it, we just subtract one (yielding -1 and 0), and then multiply by -1. Voila:

((n % 3 / (n % 3 + 1.0)).ceil - 1) * (-1)

This is our function f.

Our second conditional is the same as the first, but with checks against 5 instead of 3. So we can just copy f and replace 3 with 5, giving us

((n % 5 / (n % 5 + 1.0)).ceil - 1) * (-1)

for our function g.

To find our function h, we can use the same modulo/ceil technique. This time, we want it to return 0 if n is divisible by either 3 or 5, and 1 otherwise. Well, we just wrote some code that gave us 0 if n is divisible by 3 — the beginning of our f function, ((n % 3) / ((n % 3) + 1.0)).ceil. For divisibility by 5, the same can be extracted from g. Multiplying these together will give us 1 only if n is divisible by both 3 and 5, otherwise it will give 0. Our final function, h, is discovered:

(n % 3 / (n % 3 + 1.0)).ceil * (n % 5 / (n % 5 + 1.0)).ceil

Exercise for the reader:

  1. Write a generalized version of f and g, which works for any number, not just 3 or 5.
  2. Write a generalized version of h, which works for any two numbers, not just 3 and 5

Printing and Iterating (or not?)

Putting these all into Ruby, we have this nice piece of code that gives us the correct output for any input n. Note the n.to_s — we need to convert the number to a string, because printing 0 * 0 gives "0", whereas printing '0' * 0 gives "", an empty string. We don't want to be printing "fizz0"!

def printFizzBuzzForInput n
  print "#{'fizz' * f(n)}"
  print "#{'buzz' * g(n)}"
  print "#{n.to_s * h(n)}"
  print "\n"
end

def f n
  ((n % 3 / (n % 3 + 1.0)).ceil - 1) * (-1)
end

def g n
  ((n % 5 / (n % 5 + 1.0)).ceil - 1) * (-1)
end

def h n
  (n % 3 / (n % 3 + 1.0)).ceil * (n % 5 / (n % 5 + 1.0)).ceil
end

This covers 90% of the problem. Our full solution will allow the caller to specify a starting number and a limit to stop printing at. All that's left is to iterate from the starting number to a limit, e.g. 1 to 100, and print the correct output for each number. "Iterate?" you say, "That sounds like we'll need a for or while loop…and those use conditionals!"

The functional world isn't big on relying on state change like conditionals in loops. They have another way to work on arrays: the map function, which takes in another function and executes it on each item in an array, returning an array of the results. For our purposes, we don't care about the returned array; we just want to use the mapped function to print our fizzes and buzzes. To get an array of all the input values, we create a range of integers from the starting number and limit, like this: (start..limit), and convert it into an array to map over with the to_a function. Applying map to that array is simple, and gives us this final FizzBuzz function:

def fizzbuzz start, limit
  (start..limit).to_a.map { |n|
    printFizzBuzzForInput n
  }
end

You can see the completed code all together here.

Functional Process

When I started this post, I thought it would be a quick explanation of a trick problem. As I put my thought process in words, I realized that the techniques and methods of thinking are actually applicable to more than just trivia. I'm interested in more of these kinds of problems — something relatively simple with a constraint or twist. Pulling in knowledge from many domains and applying it makes you more flexible as a programmer.