Pure Danger Tech


navigation
home

Clojure FizzBuzz

30 Jun 2011

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