FizzBuzz
The FizzBuzz challenge is a common coding question, usually given as an introductory problem in a coding competition or interview. Any junior programmer would be expected to correctly solve the FizzBuzz question. We'll go over two different solutions in Elixir.
Directions
For the range of number 1..100, print each number but replace the word "fizz" for multiples of 3, "buzz" for multiples of 5, and "fizzbuzz" for multiples of both 3 and 5.
Examples
iex> FizzBuzz.print()
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
... # clipped for brevity
Constraints
The range should include both 1 and 100. Solve the problem using any techniques we've discussed in this book.
Note
Attempt to solve the problem by yourself. Spend approximately 10 minutes coming up with an original solution. There is nothing gained by copy /pasting someone else's solution or skipping straight to the answer. Create a new Elixir file and begin from a blank page. Start typing code, make comments about what you're trying to accomplish. Remember, try to break the problem down into smaller pieces and focus on solving one piece at a time. This is the best way to learn how to code. It will help you gain confidence in yourself and your abilities.
My approach
The easiest way to solve this problem is by iterating over each number in the range and using a cond
statement to determine what should be printed. A multiple of 3 is any number that divides by 3 evenly. Therefore, the remainder of any number, x
and 3 will ==
0. The same is true for multiples of 5. The last part, "multiples of both 3 and 5", is another way of saying "multiples of 15". Here is my first approach:
defmodule FizzBuzz do
def print() do
1..100 # create an inclusive range from 1-100
|> Enum.map(fn x -> # map over the range and for every value "x" ...
cond do # based on the condition do this
rem(x, 3) == 0 -> "fizz" # if x/3 has no remainder, print "fizz"
rem(x, 5) == 0 -> "buzz" # if x/5 has no remainder, print "buzz"
rem(x, 15) == 0 -> "fizzbuzz"# if x/15 has no remainder, print "fizzbuzz"
true -> x # all other numbers, print the number
end
end)
|> Enum.each(fn x -> IO.puts(x) end)
end
end
This looks like a viable solution to the problem. Unfortunately, we'll soon find out it's flawed. Running FizzBuzz.print()
in iex yields the following result:
iex> FizzBuzz.print
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizz
16
17
fizz
... # clipped for brevity
It starts out printing 1, 2, fizz, 4, buzz
, as expected. However, instead of displaying "fizzbuzz" for number 15, it only displays "fizz". What happened?
Notice the cond
statement checks for multiples of 3, 5, and 15 in ascending order. This is a problem because code is executed sequentially and the number 15 is, in fact, a multiple of both 3 and 5. This means that when cond
checks if x
is a multiple of 3 when x
is 15 the result is true, thus "fizz" is printed. Therefore, in order to print "fizzbuzz", we'll need to place the rem(x, 15)== 0
statement before rem(x, 3) == 0
. The module after the change should like like this:
defmodule FizzBuzz do
def print() do
1..100
|> Enum.map(fn x ->
cond do
rem(x, 15) == 0 -> "fizzbuzz" # moved this line up
rem(x, 3) == 0 -> "fizz"
rem(x, 5) == 0 -> "buzz"
true -> x
end
end)
|> Enum.each(fn x -> IO.puts(x) end)
end
end
Now executing FizzBuzz.print
yields:
# reload the module
iex> r FizzBuzz
warning: redefining module FizzBuzz (current version defined in memory)
fizzbuzz.exs:16
{:reloaded, FizzBuzz, [FizzBuzz]}
iex> FizzBuzz.print
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz # now 15 prints fizzbuzz
16
17
fizz
19
buzz
fizz
22
23
fizz
buzz
26
fizz
28
29
fizzbuzz # multiples of 15 print fizzbuzz too
... # clipped for brevity
We can look at a variation of the FizzBuzz problem, one that prints the FizzBuzz sequence up to n
, where n
is an argument passed to the function. For example, passing an argument of 30 would print the first 30 values of the FizzBuzz sequence. In this solution I'll use a completely different approach to solving the problem.
# FizzBuzz solution using streams
defmodule FizzBuzz do
def up_to(n) do
create_stream() # the up_to function starts by calling the create_stream() function
end
def create_stream do
threes = Stream.cycle([nil, nil, "fizz "])
fives = Stream.cycle([nil, nil, nil, nil, "buzz "])
Stream.zip(threes, fives)|> Steam.with_index
# TODO: iterate and print values up to n
end
end
This is a good point to pause and explain what is going on. The Stream.cycle/1
function takes a single argument, an enumerable. This creates a stream that can be cycled through, commonly by Enum.take/2
. For example, consider the following example:
iex(1)> stream = Stream.cycle([1,2,3,4,5])
#Function<64.58052446/2 in Stream.unfold/2>
iex(2)> Enum.take(stream, 4)
[1, 2, 3, 4]
iex(3)> Enum.take(stream, 10)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
When Enum.take
takes more elements then are available it continues the cycle starting back at the first element. Also, notice I'm binding the Stream.cycle
of [nil, nil, "fizz "] to the variable "threes". This is another way of saying, "replace multiples of 3 with fizz". Next, I do the same thing with multiples of 5 and "buzz ".
The Stream.zip
combines elements from a collection of enumerables into a stream of tuples. As expected, with_index
adds the index to the tuple containing the element. Recall that the |>
operator takes the result of the expression on the left and uses it as the first argument to the function on the right. To complete the FizzBuzz solution:
# FizzBuzz solution using streams
defmodule FizzBuzz do
def up_to(n) do
create_stream()
|> Enum.take(n) # pass the stream as the first argument to Enum.take
|> Enum.join()
|> IO.puts()
end
def create_stream do
threes = Stream.cycle([nil, nil, "fizz "])
fives = Stream.cycle([nil, nil, nil, nil, "buzz "])
Stream.zip(threes, fives)
|> Steam.with_index
|> Stream.map(&print/1) # capture the private print function
end
# private functions
defp print({{nil, nil}, n}), do: "#{n + 1} "
defp print({{fizz, buzz}, _}), do: "#{fizz}#{buzz}"
end
The main function, up_to
uses the argument n
to take n
elements from stream. The capture operator is used to map over the stream and indices, calling the private print function. The result is then joined and printed to the screen. Here is an example of using the FizzBuzz module in iex:
# FizzBuzz sequence using streams
iex(1)> FizzBuzz.up_to(45)
1 2 fizz 4 buzz fizz 7 8 fizz buzz
11 fizz 13 14 fizz buzz 16 17 fizz 19 buzz
fizz 22 23 fizz buzz 26 fizz 28 29 fizz
buzz 31 32 fizz 34 buzz fizz 37 38 fizz buzz
41 fizz 43 44 fizz buzz
Because we are using streams, all of the calculations are done and IO.puts
is called a single time, printing everything on a single line. Since this far exceeds the width of the average browser, I've added line breaks after every ten elements.
Here is the sequence passing a smaller value for n
.
iex(2)> FizzBuzz.up_to(15)
1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizz buzz
Is one solution better than another?
It depends on your particular use case. If you are participating in a coding challenge or technical interview you'll likely be judged by different scales. In either case, you'll want to consider the following:
- Does your solution satisfy the problem?
- What is the time/space complexity of your solution?
How long it takes you to arrive at the solution is another consideration. Universally speaking, it's more important to come up with the right answer slowly than the wrong answer quickly. However, you'll often find time constraints during interviews and your professional career so it's important to develop a balance of speed and efficiency.
The topic of time/space complexity and Big O notation are outside the scope of this text. It's generally suggested that you first solve the problem however you know how, then look for opportunities to refactor. Said another way, make it work, then make it pretty, then make it faster.