I recently came across this riff on the FizzBuzz problem written in Clojure. While it’s admittedly not terribly obvious what’s going on, I thought it was a novel solution to the FizzBuzz problem.
How could we recreate this solution using Elixir? There are some obvious similarities between Clojure’s cycle
and Elixir’s Stream.cycle/1
. As someone who’s always been a fanboy of Lisp syntax, which solution would I prefer?
There’s only one way to find out…
But First, an Explanation
Before we dive into our Elixir solution, we should work out what exactly this Clojure solution is doing:
(clojure.pprint/pprint
(map vector
(range 25)
(cycle [:fizz :_ :_])
(cycle [:buzz :_ :_ :_ :_])))
Clojure’s clojure.pprint/pprint
obviously just prints whatever’s passed into it. In this case, we’re printing the result of this expression:
(map vector
(range 25)
(cycle [:fizz :_ :_])
(cycle [:buzz :_ :_ :_ :_])))
But what exactly’s happening here? Clojure’s map
function is interesting. It let’s you map a function over any number of collections. The result of the map
expression is the result of applying the function to each of the first values of each collection, followed by the result of applying the mapped function to each of the second values, and so on.
In this case, we’re mapping the vector
function over three collections: the range of numbers from zero to twenty four ((range 25)
), the infinite cycle of :fizz
, :_
, and :_
((cycle [:fizz :_ :_])
), and the infinite cycle of :buzz
, :_
, :_
, :_
, :_
((cycle [:buzz :_ :_ :_ :_])
).
Mapping vector
over each of these collections creates a vector for each index, and whether it should display Fizz, Buzz, or FizzBuzz for that particular index.
The result looks just like we’d expect:
([0 :fizz :buzz]
[1 :_ :_]
[2 :_ :_]
[3 :fizz :_]
[4 :_ :_]
[5 :_ :buzz]
...
[24 :fizz :_])
An Elixir Solution
So how would we implement this style of FizzBuzz solution using Elixir? As we mentioned earlier, Elixir’s Stream.cycle/1
function is almost identical to Clojure’s cycle
. Let’s start there.
We’ll make two cycles of our Fizz and Buzz sequences:
Stream.cycle([:fizz, :_, :_])
Stream.cycle([:buzz, :_, :_, :_, :_])
On their own, these two cycles don’t do much.
Let’s use Stream.zip/2
to effectively perform the same operation as Clojure’s map vector
:
Stream.zip(Stream.cycle([:fizz, :_, :_]), Stream.cycle([:buzz, :_, :_, :_, :_]))
Now we can print the first twenty five pairs by piping our zipped streams into Enum.take/2
and printing the result with IO.inspect/1
:
Stream.zip(Stream.cycle([:fizz, :_, :_]), Stream.cycle([:buzz, :_, :_, :_, :_]))
|> Enum.take(25)
|> IO.inspect
Our result looks similar:
[
fizz: :buzz,
_: :_,
_: :_,
fizz: :_,
_: :_,
_: :buzz,
...
fizz: :_
]
While our solution works, I’m not completely happy with it.
Polishing Our Solution
For purely aesthetic reasons, let’s import the function’s we’re using from Stream
, Enum
and IO
:
import Stream, only: [cycle: 1, zip: 2]
import Enum, only: [take: 2]
import IO, only: [inspect: 1]
This simplifies the visual complexity of our solution:
zip(cycle([:fizz, :_, :_]), cycle([:buzz, :_, :_, :_, :_]))
|> take(25)
|> inspect
But we can take it one step further.
Rather than using Stream.zip/2
, which expects a left
and right
argument, let’s use Stream.zip/1
, which expects to be passed an enumerable of streams:
[
cycle([:fizz, :_, :_]),
cycle([:buzz, :_, :_, :_, :_])
]
|> zip
|> take(25)
|> inspect
And that’s our final solution.
Final Thoughts
To be honest, I’ve been having troubles lately coming to terms with some of Elixir’s aesthetic choices. As someone who’s always admired the simplicity of Lisp syntax, I fully expected myself to prefer the Clojure solution over the Elixir solution.
That being said, I hugely prefer the Elixir solution we came up with!
The overall attack plan of the algorithm is much more apparent. It’s immediately clear that we start with two cycles of :fizz
/:buzz
and some number of empty atoms. From there, we zip together the streams and take the first twenty five results. Lastly, we inspect the result.
Which solution do you prefer?