Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
fizzbuzz example spec
(ns hellospec.fizzbuzz
(:require [clojure.spec :as s]
[clojure.spec.test :as stest]
;; to accomodate KLIPSE:
;; "[...]cljs.spec.gen.frequency is undefined[...]"
#?(:clj [clojure.spec.gen :as gen]
:cljs [clojure.test.check.generators :as gen])))
;; A fizzbuzz which doesn't use condition checking/branching
(defn fizzbuzz
"Generates n fizzbuzz sequence elements, with Fizz and Buzz for multiples of m1 respectively.
m1 and m2 must be positive integers and n non-negative, and m1 < m2."
([m1 m2 n]
(let [c #(->> % vector (concat (-> %2 dec (repeat nil))) cycle)
fb-re #"FizzBuzz|Fizz|Buzz"
format (fn [f b x]
(-> (re-matches fb-re (str f b))
(conj x)))]
(->> [(c "Fizz" m1), (c "Buzz" m2), (next (range))]
(apply map #(some identity (format % %2 %3)))
(take n))))
(fizzbuzz 3 5 n)))
;; fizzbuzz argument generation
(def ^:dynamic *max-limit* 100)
(defn max-arg [] (* 2 *max-limit*))
(defn posint [& [pred]]
(s/gen (s/and ::zero+ (or pred (constantly true)))))
(defn fizzbuzz-args-gen []
;;1-arg, limit for basic 3,5 fizzbuzz
[[1 (gen/fmap vector (s/gen (s/and int? pos? #(< % *max-limit*))))]
;;3-arg: must have fizz <= buzz
(gen/fmap (fn [[x y z]]
(-> (sort [x y])
(conj z)))
(gen/tuple (posint #(< % (max-arg)))
(posint #(< % (max-arg)))
(posint #(< % *max-limit*))))]]))
(s/def ::zero+ nat-int?)
(s/def ::fizzbuzz-args
(s/alt :_1-arg (s/cat :limit ::zero+)
:_3-arg (s/cat :n1 (s/and ::zero+ #(< 1 %))
:n2 ::zero+
:limit ::zero+))
(fn [[_ {:keys [n1 n2 limit]}]]
(if n1
(< n1 n2)
(defn n1-n2-limit-ret
"Destructures conformed fizzbuzz arguments and yields a uniform
[fizz-multiple, buzz-multiple, limit, return-value] vector"
[{:keys [args ret]}]
(let [arg-vals (second args)
limit (:limit arg-vals)]
(case (first args)
:_1-arg [3 5 limit ret]
:_3-arg [(:n1 arg-vals) (:n2 arg-vals) limit ret])))
;; A validator, uses multiples' checks and condition branching
(defn fizzbuzz-inout-valid?
"Verifies the relationship b/w conformed arguments and return value
of a fizzbuzz invocation"
(let [[mult1 mult2 limit ret] (n1-n2-limit-ret args&ret)]
(and (= limit (count ret))
(->> ret
(map (fn [i [tag x]]
(= 0 (rem i mult1)(rem i mult2))
(= :fizzbuzz tag),
(zero? (rem i mult1))
(= :fizz tag),
(zero? (rem i mult2))
(= :buzz tag),
(= i x)))
(range 1 (inc limit)))
(every? identity)))))
(s/def ::fizzbuzz-ret (s/* (s/alt :num ::zero+
:fizz (s/and string? #(= "Fizz" %))
:buzz (s/and string? #(= "Buzz" %))
:fizzbuzz (s/and string? #(= "FizzBuzz" %)))))
(s/fdef fizzbuzz
:args ::fizzbuzz-args
:ret ::fizzbuzz-ret
:fn fizzbuzz-inout-valid?)
(defn instrument []
(stest/instrument `fizzbuzz))
(defn exercise []
(s/exercise-fn `fizzbuzz))
(defn check
(stest/check `fizzbuzz
;; somehow KLIPSE doesn't recognize stc/opts
{#?(:clj :clojure.spec.test.check/opts
:cljs :clojure.test.check/opts) {:num-tests num-tests}}))
(check 10)))
;; (instrument)
;; (exercise)
;; These succeed on and off, and rarely/never consistently with one another
;; (gen/sample (fizzbuzz-args-gen) 200)
;; (check 200)
;; (ncreasing the limit succeeds on the JVM
;; (binding [*max-limit* 500] (check 200))
;;;; WHY USE *max-limit* ? ;;;;;
;; Even though the arguments are expected to grow
;; gradually, it may not be gradual enough to run
;; more tests within reasonable time.
;; For example, the following will take forever:
;; (binding [*max-limit* 100000] (check 2000))
;; ...but the same number of tests with a smaller limit
;; takes a few seconds on the JVM:
;; (binding [*max-limit* 1000] (check 2000))
(println "Running 50 tests. This may take a few seconds...")
(let [results (check 50)]
(println "Done.")
Copy link

viebel commented Feb 9, 2017

Copy link

KingCode commented Feb 9, 2017

Thank you Yehonathan, fantastic CR presentation! The current version runs fine on the JVM, but I get an error in Klipse because I am using JVM regex'es. Will change that for my next version..

Copy link

KingCode commented Feb 10, 2017

Putting FizzBuzz at the start of the regex fixed the failing tests in KLIPSE/cljs (actually browser freezes, as opposed to failing before the change). **EDIT:**After much wrestling (including reader-conditionals) and writing custom generators, (check) runs fine up to about num-tests = 100 on my JVM and KLIPSE. Increasing the allowable input (*max-limit*) increases successful num-tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment