patternMinor
Clojure Tic Tac Toe solver
Viewed 0 times
clojuretoetictacsolver
Problem
I am going through the 4clojure problems and I currently just solved the Tic Tac Toe analyzer:
I'm looking for any feedback about how I could make the code more terse.
Could I have created the diagonal vectors in one expression?
I don't like that I had to use recursion but I could not think of a way of using something like reduce because I don't think there is a way to short circuit reduce if you find the result midway through processing all the values.
Is there a way that I could avoid recursion with this problem?
(fn [board]
(let [vr (for [x (range 3)]
(get-in board[(+ x 0) (+ x 0)]))
vl (for [x (range 3)]
(get-in board [(+ x) (- 2 x)]))
columns (apply map vector board)
checks (concat board [vl] [vr] columns)]
(letfn [(check-board [[row & res]]
(if row
(let [dist (distinct row)]
(if (or (= dist [:o]) (= dist [:x]))
(first dist)
(check-board res)))
nil))]
(check-board checks))))I'm looking for any feedback about how I could make the code more terse.
Could I have created the diagonal vectors in one expression?
I don't like that I had to use recursion but I could not think of a way of using something like reduce because I don't think there is a way to short circuit reduce if you find the result midway through processing all the values.
Is there a way that I could avoid recursion with this problem?
Solution
Some Minor Points
You can omit the
Idiomatic way of checking if something is one of a number of things is using a set of those values as a predicate:
Rename
Your Questions
Could I have created the diagonal vectors in one expression?
Yes. If you factor out the differing portions, namely
Since
Is there a way that I could avoid recursion with this problem?
Recursive processing of collections/sequences can be straightforwardly transformed to
becomes this:
Note
These Changes Applied Altogether
Final Point
Also check out
(+ x 0) and (+x) can be replaced by just x, in (get-in board[(+ x 0) (+ x 0)])) and (get-in board [(+ x) (- 2 x)])) respectively.You can omit the
nil in else position in if : (if row ... nil) \$\rightarrow\$ (if row ...)Idiomatic way of checking if something is one of a number of things is using a set of those values as a predicate:
(or (= dist [:o]) (= dist [:x])) \$\rightarrow\$ (#{[:o] [:x]} dist)Rename
row to something not misleading. Since you named the collection checks you could name the iteration variable check.Your Questions
Could I have created the diagonal vectors in one expression?
Yes. If you factor out the differing portions, namely
[x x] and [x (- 2 x)], you get:(let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
(for [x (range 3)]
(get-in board (f x))))Since
diagonals is already a sequence, you replacechecks (concat board [vl] [vr] columns)] with checks (concat board diagonals columns)Is there a way that I could avoid recursion with this problem?
Recursive processing of collections/sequences can be straightforwardly transformed to
for expressions. Your :(letfn [(check-board [[row & res]]
(if row
(let [dist (distinct row)]
(if (or (= dist [:o]) (= dist [:x]))
(first dist)
(check-board res)))
nil))]
(check-board checks))))becomes this:
(first
(for [row checks
:let [dist (distinct row)]
:when (or (= dist [:o]) (= dist [:x]))]
(first dist)))Note
for is lazy, therefore (first (for ... is equivalent to your short-circuit return.These Changes Applied Altogether
(fn [board]
(let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
(for [x (range 3)]
(get-in board (f x))))
columns (apply map vector board)
checks (concat board diagonals columns)]
(first
(for [check checks
:let [dist (distinct check)]
:when (#{[:o] [:x]} dist)]
(first dist)))))Final Point
Also check out
some, every?, keep, etc. which process collections with a predicate; and which this exercise probably wants you to learn. For example some is probably what you mean by short-cut evaluation reduce.Code Snippets
(let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
(for [x (range 3)]
(get-in board (f x))))(letfn [(check-board [[row & res]]
(if row
(let [dist (distinct row)]
(if (or (= dist [:o]) (= dist [:x]))
(first dist)
(check-board res)))
nil))]
(check-board checks))))(first
(for [row checks
:let [dist (distinct row)]
:when (or (= dist [:o]) (= dist [:x]))]
(first dist)))(fn [board]
(let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
(for [x (range 3)]
(get-in board (f x))))
columns (apply map vector board)
checks (concat board diagonals columns)]
(first
(for [check checks
:let [dist (distinct check)]
:when (#{[:o] [:x]} dist)]
(first dist)))))Context
StackExchange Code Review Q#62979, answer score: 2
Revisions (0)
No revisions yet.