Skip to content

Instantly share code, notes, and snippets.

@dchelimsky
Last active August 10, 2020 22:38
Show Gist options
  • Save dchelimsky/6cf747aa36c3d60031f51f435b7eb009 to your computer and use it in GitHub Desktop.
Save dchelimsky/6cf747aa36c3d60031f51f435b7eb009 to your computer and use it in GitHub Desktop.
(ns codebreaker
(:require [clojure.spec :as s]
[clojure.spec.gen :as gen]
[clojure.spec.test :as stest]))
(comment
(score [:r :y :g :b] [:r :g :b :b])
;; => {::exact-matches 2 ;; :r and :b
;; ::loose-matches 1} ;; :g
)
(def peg? #{:y :g :r :c :w :b})
(s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
(s/fdef score
:args (s/cat :secret ::code :guess ::code))
(comment
(s/exercise (:args (s/get-spec `score)))
)
(s/fdef score
:args (s/and (s/cat :secret ::code :guess ::code)
(fn [{:keys [secret guess]}]
(= (count secret) (count guess)))))
(comment
(s/exercise (:args (s/get-spec `score)))
)
(s/def ::exact-matches nat-int?)
(s/def ::loose-matches nat-int?)
(s/fdef score
:args (s/and (s/cat :secret ::code :guess ::code)
(fn [{:keys [secret guess]}]
(= (count secret) (count guess))))
:ret (s/keys :req [::exact-matches ::loose-matches]))
(comment
(s/exercise (:ret (s/get-spec `score)))
;; note that some values are too high
)
(s/fdef score
:args (s/and (s/cat :secret ::code :guess ::code)
(fn [{:keys [secret guess]}]
(= (count secret) (count guess))))
:ret (s/keys :req [::exact-matches ::loose-matches])
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 (apply + (vals ret)) (count secret))))
(comment
(s/exercise-fn `score)
;; NPE
(s/exercise-fn #'score)
;; Unable to resolve var: score in this context
)
(defn score [secret guess]
{::exact-matches 0
::loose-matches 0})
(comment
(s/exercise-fn `score)
(stest/check `score)
)
(defn score [secret guess]
{::exact-matches 4
::loose-matches 3})
(comment
(s/exercise-fn `score)
(stest/check `score)
;; fails
)
(defn score [secret guess]
{::exact-matches (count (filter true? (map = secret guess)))
::loose-matches 0})
(comment
(s/exercise-fn `score)
(stest/check `score)
)
(defn exact-matches [secret guess]
(count (filter true? (map = secret guess))))
(defn score [secret guess]
{::exact-matches (exact-matches secret guess)
::loose-matches 0})
(s/def ::secret-and-guess (s/and (s/cat :secret ::code :guess ::code)
(fn [{:keys [secret guess]}]
(= (count secret) (count guess)))))
(s/fdef score
:args ::secret-and-guess
:ret (s/keys :req [::exact-matches ::loose-matches])
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 (apply + (vals ret)) (count secret))))
(s/fdef exact-matches
:args ::secret-and-guess
:ret nat-int?
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 ret (count secret))))
(comment
(s/exercise-fn `exact-matches)
(stest/check `exact-matches)
(stest/instrument `exact-matches)
(s/exercise-fn `score)
(stest/check `score)
)
;; introduce bug
(defn score [secret guess]
{::exact-matches (exact-matches secret (take 3 guess))
::loose-matches 0})
(comment
;; this will expose it
(s/exercise-fn `score)
)
;; remove bug
(defn score [secret guess]
{::exact-matches (exact-matches secret guess)
::loose-matches 0})
(s/fdef match-count
:args ::secret-and-guess
:ret nat-int?
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 ret (count secret))))
(comment
(s/exercise-fn `exact-matches 10 (s/get-spec `match-count))
(stest/check-fn exact-matches (s/get-spec `match-count))
(stest/instrument `exact-matches {:spec {`exact-matches (s/get-spec `exact-matches)}})
(s/exercise-fn `score)
(stest/check `score)
)
(defn all-matches [secret guess]
(apply + (vals (merge-with min
(select-keys (frequencies secret) guess)
(select-keys (frequencies guess) secret)))))
(comment
(s/exercise-fn `all-matches 10 (s/get-spec `match-count))
)
(defn score [secret guess]
(let [exact (exact-matches secret guess)
all (all-matches secret guess)]
{::exact-matches exact
::loose-matches (- all exact)}))
(comment
(stest/instrument [`exact-matches `all-matches]
{:spec {`exact-matches (s/get-spec `match-count)
`all-matches (s/get-spec `match-count)}})
(s/exercise-fn `score)
(stest/check `score)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;; END RESULT
(def peg? #{:y :g :r :c :w :b})
(s/def ::code (s/coll-of peg? :min-count 4 :max-count 6))
(s/def ::secret-and-guess (s/and (s/cat :secret ::code :guess ::code)
(fn [{:keys [secret guess]}]
(= (count secret) (count guess)))))
(s/fdef score
:args ::secret-and-guess
:ret (s/keys :req [::exact-matches ::loose-matches])
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 (apply + (vals ret)) (count secret))))
(s/fdef match-count
:args ::secret-and-guess
:ret nat-int?
:fn (fn [{{secret :secret} :args ret :ret}]
(<= 0 ret (count secret))))
(defn exact-matches [secret guess]
(count (filter true? (map = secret guess))))
(defn all-matches [secret guess]
(apply + (vals (merge-with min
(select-keys (frequencies secret) guess)
(select-keys (frequencies guess) secret)))))
(defn score [secret guess]
(let [exact (exact-matches secret guess)
all (all-matches secret guess)]
{::exact-matches exact
::loose-matches (- all exact)}))
(comment
(stest/instrument [`exact-matches `all-matches]
{:spec {`exact-matches (s/get-spec `match-count)
`all-matches (s/get-spec `match-count)}})
(stest/summarize-results (stest/check 'codebreaker/score))
(let [result (stest/summarize-results (stest/check 'codebreaker/score))]
(and (= (:total result) (:check-passed result))
(not (contains? result :check-failed))))
)
@funkrider
Copy link

Hey,
was trying to code along with your blog but ran into issues related to namespaces and required libraries. In particular you must read a long way down the spec guide before you discover that the exercise function requires the test.check library. Perhaps you could update as in:

(ns codebreaker
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]
[clojure.spec.test.alpha :as stest]))

;; clojure.spec requires clojure >= 1.9.0 (current is 1.9.0 alpha 17 but you should check online for latest)
;; and clojure.spec.test depends upon test.check for various methods including exercise.
;; For leiningen your project.clj must include these dependencies
;; :dependencies [ [org.clojure/clojure "1.9.0-alpha17"]
;; [org.clojure/test.check "0.9.0"] ]

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