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")