Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8...

14
Solving N-Queens in Clojure

Transcript of Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8...

Page 1: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Solving N-Queens in Clojure

Page 2: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

The N-Queens Problem

• The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

• Franz Nauck in 1850 extended the chess problem to n-queens problem on an n×n board.

• S. Günther proposed a method of finding solutions by using matrix determinants.

• Edsger Dijkstra used this problem in 1972 to illustrate the power of what he called structured programming.

Page 3: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Solution Design

• Let us represent solution using vectors of column locations.• [5 3 6 0 7 1 4 2] is the solution above• We can write a generator that adds a queen to a new row as

long as it is non-attacking. • A simple recursive generator function n-queens will

– Take each solution to n-1 queens problem and apply map – which-queen is function to computes list of possible additions

(we’ll use list comprehensions/for simplicity)– map the conjall using which queen to add to each partial solution– So n_queens() will take 2 parameters, the number of queens n

(same as # rows) and m (or number of cols) of the board.

Page 4: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Pattern for n-queens coming from allchains solution

(defn allchains [n m] (cond (= n 0) '(()) :else (apply concat (map (fn [it] (conjall (range 1 (inc m)) it)) (allchains (dec n) m))))) (defn n-queens [n m] (cond (= n 0) '([]) :else (apply concat (map (fn [it] (conjall (which-queens it m) it)) (n-queens (dec n) m))))

Page 5: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Recall conjall for generating collectionsconj is the standard op for building collections in Clojure. conj returns a new collection with the new item 'added'. The 'addition' may happen at different 'places' depending on the concrete type.user=> (conj [1 2 3] 4) => [1 2 3 4]user=> (conj '(1 2 3) 4) => (4 1 2 3)

We write conjall with input a vector vec and a list lst… and returns a collection of all conj’s of lst elements onto the vec.(defn conjall [lst vec] (cond (empty? lst) '() :else (conj (conj vec (first lst))

(conjall (rest lst1) vec)));(conjall '(4 5 6) [ 1 2 ]); => ([1 2 4] [1 2 5] [1 2 6])

Page 6: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

List Comprehension using for List comprehension uses for for generating lists. Takes a vector of one or more binding-form/collection-expr pairs, each followed by zero or more modifiers, and yields a lazy sequence of evaluations of expr.

(for [x (range 6) y (range 5) :let [z (* x y)]

:when (odd? z)] (list x y));=> ((1 1) (1 3) (3 1) (3 3) (5 1) (5 3))

:when iterates over the bindings, but only evaluates the body of the loop when the condition is true.:while iterates over the bindings and evaluates the body until the condition is false:

(for [x (range 20) :when (not= x 10)] x) ; =>(0 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18 19) (for [x (range 20) :while (not= x 10)] x) ; => (0 1 2 3 4 5 6 7 8 9)

Page 7: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Which-queens to add?; for each possible col x return x ; if all other queens in partial sol psol are non-attacking

(defn which-queens [psol m] (for [x (range m) :when (not-any? true? (for [i (range (count psol)) :let [pi (psol i)]] (or ;check if pi and x share col or diagonal (= pi x) (= (- (count psol) i) (Math/abs (- x pi)))) )) ]

x))

Page 8: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

That all! 92 solutions(defn n-queens [n m] (cond (= n 0) '([]) :else (apply concat (map (fn [it] (conjall (which-queens it m) it)) (n-queens (dec n) m))))user=> (count (n-queens 8 8))92user=> (n-queens 8 8)([0 4 7 5 2 6 1 3] [0 5 7 2 6 3 1 4] [0 6 3 5 7 1 4 2] [0 6 4 7 1 3 5 2] [1 3 5 7 2 0 6 4] [1 4 6 0 2 7 5 3] [1 4 6 3 0 7 5 2] [1 5 0 6 3 7 2 4] [1 5 7 2 0 3 6 4] [1 6 2 5 7 4 0 3] [1 6 4 7 0 3 5 2] [1 7 5 0 2 4 6 3] [2 0 6 4 7 1 3 5] [2 4 1 7 0 6 3 5] [2 4 1 7 5 3 6 0] [2 4 6 0 3 1 7 5] [2 4 7 3 0 6 1 5] [2 5 1 4 7 0 6 3] [2 5 1 6 0 3 7 4] [2 5 1 6 4 0 7 3] [2 5 3 0 7 4 6 1] [2 5 3 1 7 4 6 0] [2 5 7 0 3 6 4 1] [2 5 7 0 4 6 1 3] [2 5 7 1 3 0 6 4] [2 6 1 7 4 0 3 5] [2 6 1 7 5 3 0 4] [2 7 3 6 0 5 1 4] [3 0 4 7 1 6 2 5] [3 0 4 7 5 2 6 1] [3 1 4 7 5 0 2 6] [3 1 6 2 5 7 0 4] [3 1 6 2 5 7 4 0] [3 1 6 4 0 7 5 2] [3 1 7 4 6 0 2 5] [3 1 7 5 0 2 4 6] [3 5 0 4 1 7 2 6] [3 5 7 1 6 0 2 4] [3 5 7 2 0 6 4 1] [3 6 0 7 4 1 5 2] [3 6 2 7 1 4 0 5] [3 6 4 1 5 0 2 7] [3 6 4 2 0 5 7 1] [3 7 0 2 5 1 6 4] [3 7 0 4 6 1 5 2] [3 7 4 2 0 6 1 5] [4 0 3 5 7 1 6 2] [4 0 7 3 1 6 2 5] [4 0 7 5 2 6 1 3] [4 1 3 5 7 2 0 6] [4 1 3 6 2 7 5 0] [4 1 5 0 6 3 7 2] [4 1 7 0 3 6 2 5] [4 2 0 5 7 1 3 6] [4 2 0 6 1 7 5 3] [4 2 7 3 6 0 5 1] [4 6 0 2 7 5 3 1] [4 6 0 3 1 7 5 2] [4 6 1 3 7 0 2 5] [4 6 1 5 2 0 3 7] [4 6 1 5 2 0 7 3] [4 6 3 0 2 7 5 1] [4 7 3 0 2 5 1 6] [4 7 3 0 6 1 5 2] [5 0 4 1 7 2 6 3] [5 1 6 0 2 4 7 3] [5 1 6 0 3 7 4 2] [5 2 0 6 4 7 1 3] [5 2 0 7 3 1 6 4] [5 2 0 7 4 1 3 6] [5 2 4 6 0 3 1 7] [5 2 4 7 0 3 1 6] [5 2 6 1 3 7 0 4] [5 2 6 1 7 4 0 3] [5 2 6 3 0 7 1 4] [5 3 0 4 7 1 6 2] [5 3 1 7 4 6 0 2] [5 3 6 0 2 4 1 7] [5 3 6 0 7 1 4 2] [5 7 1 3 0 6 4 2] [6 0 2 7 5 3 1 4] [6 1 3 0 7 4 2 5] [6 1 5 2 0 3 7 4] [6 2 0 5 7 4 1 3] [6 2 7 1 4 0 5 3] [6 3 1 4 7 0 2 5] [6 3 1 7 5 0 2 4] [6 4 2 0 5 7 1 3] [7 1 3 0 6 4 2 5] [7 1 4 2 0 6 3 5] [7 2 0 5 1 4 6 3] [7 3 0 2 5 1 6 4])

Page 9: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Isomorph Rejection Problem • Not all of the 92 solutions found can be considered unique, in the sense that

rotating or flipping the board around can result in another solution found in the set.

• 8 transformations that map the chess-board to itself; 4 rotations of 90 degrees, and 4 reflections --the so-called dihedral group D8 of automorphisms of the square.

• A solution (based on perfect hashing) is to consider each solution of N-Queens as a base N+1 number. We can generate solutions in numeric order, and test if a solution is isomorphic to a previously found solution if and only if one of the 8 transformations produces a solution, which is numerically (or, more generally, lexicographically) less than the original

We can lexicographically compare solutions as follows…user=> (compare [0 4 7 5 2 6 1 3] [7 1 3 0 6 4 2 5])-1

Page 10: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Working with immutability is sometimes difficult

(This may not be best work around) To work in an immutable fashion we will expand and collapse each vector using a list of [row-index col-index] pairs as intermediate solution.

; (def a (first (n-queens 8 8))(defn expand [sol] (map vector (range 8) sol) );(expand a);=>([0 7] [1 3] [2 0] [3 2] [4 5] [5 1] [6 6] [7 4])

If [i j] is a queen in sol, then [j 7-i] is a queen in (rotate sol)If [i j] is a queen in sol, then [i 7-j] is a queen in (reflect sol)

(defn rotate [sol] ( map (fn[x] (let [[ i j] x] (vector j (- 7 i)))) sol))

(defn reflect [sol] ( map (fn[x] (let [[ i j] x] (vector i (- 7 j)))) sol))

Page 11: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Expand and Collapse Transformed Solutions

(reflect (expand a));=> ([0 7] [1 3] [2 0] [3 2] [4 5] [5 1] [6 6] [7 4]); (sort (rotate (expand a)));=> ([0 7] [1 1] [2 3] [3 0] [4 6] [5 4] [6 2] [7 5])

(defn collapse [p] (into [] (map (fn[ij] (last ij)) p)))

;(collapse '([0 7] [1 1] [2 3] [3 0] [4 6] [5 4] [6 2] [7 5]));=> [7 1 3 0 6 4 2 5]

Page 12: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

into lets you take anything seq'able

• Take a list, vector, map, set, sorted-map and an empty container you want filled.

• (into [] '(1 2 3 4)) ==> [1 2 3 4] "have a lazy list and want a vector"

• into #{} [1 2 3 4]) ==> #{1 2 3 4} "have a vector and want a set"

• > (into {} #{[1 2] [3 4]}) ==> {3 4, 1 2} "have a set of vectors want a map"

• > (into #{} [{1 2} {3 4}]) ==> #{{1 2} {3 4}} "have a vector of maps want a set of maps"

Page 13: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Here is a potential solution

• (defn non-iso [n] (filter canonical-pred? (n-queens n n))

Page 14: Solving N-Queens in Clojure. The N-Queens Problem The classic 8-queens problem is that of placing 8 queens on a chessboard so that no pair is attacking.

Homework #3

• How many orbits/non-iso solutions?• How many are full (size 8) and degenerate?• Catalog the fixed configurations of 8

symmetries.• Write clojure program to produce list of all

canonical (non-isomorphic) solution vectors.