Realized I had never done FizzBuzz in Clojure. FizzBuzz says for numbers 1 to 100,
- Print “Fizz” if number is a multiple of 3
- Print “Buzz” if number is a multiple of 5
- Print “FizzBuzz” if number is a multiple of both
- Otherwise, print the number
Here’s my (failing) test:
(ns fizzbuzz
(:use [ clojure.test ]))
(defn fizzbuzz [upto]
"TODO")
(deftest test-fizzbuzz
(is (= [1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz"]
(fizzbuzz 15))))
The most natural translation of requirement to code I could write:
(defn fb [x]
(if (= 0 (mod x 3))
(if (= 0 (mod x 5))
"FizzBuzz"
"Fizz")
(if (= 0 (mod x 5))
"Buzz"
x)))
(defn fizzbuzz [upto]
(map fb (range 1 (inc upto))))
I don’t like those (= 0 (mod x n)) though. Rerwote with a mult-of function:
(defn mult-of [x m]
(zero? (mod x m)))
(defn fb [x]
(if (mult-of x 3)
(if (mult-of x 5)
"FizzBuzz"
"Fizz")
(if (mult-of x 5)
"Buzz"
x)))
Better. That repeated (mult-of x 5) bugged me though. I really want to apply both mult-of 3 and mult-of 5 to x once. Added a new mult-say that checked if x is a multiple of n and if so returns what it should say, otherwise nil. Apply mult-say with 3 “Fizz” and 5 “Buzz” and combine the results into a string. If the string is empty, return x, otherwise the string.
(defn mult-say [x n say]
(when (mult-of x n) say))
(defn fb [x]
(let [mults (str (mult-say x 3 "Fizz")
(mult-say x 5 "Buzz"))]
(if (empty? mults) x mults)))
Kind of bugs me that the 3 “Fizz” 5 “Buzz” stuff is encoded into the function. Why not have a general form of this that takes a specification of what multiples and what strings to print?
We’ll need to alter our test a bit to pass the specification as well as the code of course. Here’s the whole updated mess:
(ns fizzbuzz
(:use [ clojure.test ]))
(defn mult-of [x m]
(zero? (mod x m)))
(defn mult-say [x n say]
(when (mult-of x n) say))
(defn fb [specs x]
(let [spec-fn (fn [[m say]] (mult-say x m say))
mults (apply str (map spec-fn specs))]
(if (empty? mults) x mults)))
(defn fizzbuzz [specs upto]
(map #(fb specs %) (range 1 (inc upto))))
(deftest test-fizzbuzz
(is (= [1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz"]
(fizzbuzz {3 "Fizz" 5 "Buzz"} 15))))
And now we can also use fizz buzz to do alternate versions:
fizzbuzz> (fizzbuzz {2 "bop" 3 "fizz" 5 "buzz"} 30)
(1 "bop" "fizz" "bop" "buzz" "bopfizz" 7 "bop" "fizz" "bopbuzz" 11 "bopfizz" 13 "bop"
"fizzbuzz" "bop" 17 "bopfizz" 19 "bopbuzz" "fizz" "bop" 23 "bopfizz" "buzz" "bop"
"fizz" "bop" 29 "bopfizzbuzz")
