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.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
(cycle [:fizz :_ :_])), and the infinite cycle of
(cycle [:buzz :_ :_ :_ :_])).
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.
Stream.zip/2 to effectively perform the same operation as Clojure’s
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
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
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
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.
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
: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?