140 likes | 389 Views
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. Franz Nauck in 1850 extended the chess problem to n-queens problem on an n×n board.
E N D
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. • EdsgerDijkstra used this problem in 1972 to illustrate the power of what he called structured programming.
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.
Pattern for n-queens coming from allchains solution (defnallchains [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))))
Recall conjall for generating collections conj 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. (defnconjall [lstvec] (cond (empty? lst) '() :else (conj (conjvec (first lst)) (conjall (rest lst1) vec))) ;(conjall '(4 5 6) [ 1 2 ]) ; => ([1 2 4] [1 2 5] [1 2 6])
List Comprehension using for List comprehension uses forfor 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)
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 (psoli)]] (or ;check if pi and x share color diagonal (= pi x) (= (- (count psol) i) (Math/abs (- x pi)))) )) ] x))
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)) 92 user=> (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])
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
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))
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]
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"
Here is a potential solution • (defn non-iso [n] (filter canonical-pred? (n-queens n n))
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.