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:

  1. Does your solution satisfy the problem?
  2. 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.

results matching ""

    No results matching ""