├── .gitignore ├── README.md ├── project.clj ├── spec └── sudoku │ └── core_spec.clj └── src └── sudoku └── core.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .nrepl-port 2 | /target 3 | /lib 4 | /classes 5 | /checkouts 6 | pom.xml 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .idea/ 14 | *.iml 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sudoku 2 | 3 | ### Testing Strategy. 4 | Sudoku boards are traditionally rank 3. That means there are 3^2 rows 5 | and 3^2 columns and 3^2 possible digits. 6 | 7 | We will begin testing with a rank 1 board. Such a board has one row, 8 | one column, and only one valid number: 1. Thus there is only one solution. `[[1]]`. 9 | 10 | Then we'll move on to the rank 2 board. This has 4 rows, 4 columns, and 11 | 4 valid numbers `[1 2 3 4]`. They have the form: 12 | 13 | [[d d d d] 14 | [d d d d] 15 | [d d d d] 16 | [d d d d]] 17 | 18 | Once that is solved, I expect the general case to be solved as well. 19 | 20 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject sudoku "0.1.0-SNAPSHOT" 2 | :description "Sudoku Solver" 3 | :license {:name "No Restrictions"} 4 | :main sudoku.core 5 | :dependencies [[org.clojure/clojure "1.8.0"]] 6 | :profiles {:dev {:dependencies [[speclj "3.3.2"]]}} 7 | :plugins [[speclj "3.3.2"]] 8 | :test-paths ["spec"]) 9 | -------------------------------------------------------------------------------- /spec/sudoku/core_spec.clj: -------------------------------------------------------------------------------- 1 | (ns sudoku.core-spec 2 | (:require [speclj.core :refer :all] 3 | [sudoku.core :refer :all])) 4 | 5 | (def N nil) 6 | 7 | ;This is the degenerate case. A sudoku board of rank 1. Such a board has only one possible solution 8 | ;which is [[1]]. 9 | (describe 10 | "a rank 1 game" 11 | (context 12 | "it has only one solution [[1]]" 13 | (it "should solve [[N]]" 14 | (should= [[[1]]] (solve-board 1 [[N]]))))) 15 | 16 | (describe 17 | "a rank 2 game" 18 | )(context 19 | "has groupings consisting of columns, rows, and sectors" 20 | (let [board [[1 2 3 4] 21 | [2 3 4 1] 22 | [4 3 2 1] 23 | [3 2 1 4]]] 24 | (it "should extract rows" 25 | (should= [1 2 3 4] (extract-row 0 board)) 26 | (should= [2 3 4 1] (extract-row 1 board))) 27 | (it "should extract columns" 28 | (should= [1 2 4 3] (extract-column 0 board)) 29 | (should= [2 3 3 2] (extract-column 1 board))) 30 | (it "should extract sectors" 31 | (should= [1 2 2 3] (extract-sector 2 [0 0] board)) 32 | (should= [3 4 4 1] (extract-sector 2 [1 0] board)) 33 | (should= [2 1 1 4] (extract-sector 2 [1 1] board))))) 34 | 35 | (describe 36 | "The sudoku solver" 37 | (context 38 | "It can identify missing cells" 39 | (it "will find the missing cell in a rank 1 board" 40 | (should= [[0 0]] (find-missing-cells 1 [[N]])) 41 | (should= [] (find-missing-cells 1 [[1]]))) 42 | (it "will find missing dells in a rank 2 board" 43 | (should= (set [[2 1] [1 2]]) 44 | (set (find-missing-cells 2 [[1 4 3 4] 45 | [3 4 N 2] 46 | [2 N 4 1] 47 | [4 1 2 3]])))) 48 | ) 49 | 50 | (context 51 | "It can find the possible values for the missing cells" 52 | (it "will find the only possible value for rank 1" 53 | (should= #{1} (find-possible-values 1 [0 0] [[N]]))) 54 | (it "will find the missing values for rank 2" 55 | (should= #{2} (find-possible-values 2 [1 0] [[1 N 3 4] 56 | [3 4 1 2] 57 | [2 3 4 1] 58 | [4 1 2 3]])) 59 | (should= #{4 2} (find-possible-values 2 [2 2] [[1 2 3 4] 60 | [3 4 1 2] 61 | [N 3 N N] 62 | [4 1 N N]])) 63 | ) 64 | 65 | ) 66 | 67 | (it 68 | "can set the cell of a board" 69 | (should= [[1]] (set-cell [[N]] [0 0] 1)) 70 | (should= [[1 2 3 4] 71 | [2 3 4 1] 72 | [4 3 2 1] 73 | [2 4 1 3]] (set-cell [[1 2 3 4] 74 | [2 3 1 1] 75 | [4 3 2 1] 76 | [2 4 1 3]] [2 1] 4))) 77 | 78 | (it 79 | "can solve the rank 2 board" 80 | (should= [[[1 2 3 4] 81 | [3 4 1 2] 82 | [2 3 4 1] 83 | [4 1 2 3]]] (solve-board 2 [[1 2 3 4] 84 | [3 4 1 2] 85 | [N 3 N 1] 86 | [4 1 N 3]]))) 87 | 88 | (it 89 | "can solve the rank 2 boards with multiple solutions" 90 | (should= [[[1 2 3 4] 91 | [3 4 1 2] 92 | [4 3 2 1] 93 | [2 1 4 3]] 94 | [[1 2 3 4] 95 | [3 4 1 2] 96 | [2 3 4 1] 97 | [4 1 2 3]]] (solve-board 2 [[1 2 3 4] 98 | [3 4 1 2] 99 | [N 3 N 1] 100 | [N 1 N 3]]))) 101 | 102 | (it 103 | "can solve rank 3" 104 | (should= [[[4 3 5 2 6 9 7 8 1] 105 | [6 8 2 5 7 1 4 9 3] 106 | [1 9 7 8 3 4 5 6 2] 107 | [8 2 6 1 9 5 3 4 7] 108 | [3 7 4 6 8 2 9 1 5] 109 | [9 5 1 7 4 3 6 2 8] 110 | [5 1 9 3 2 6 8 7 4] 111 | [2 4 8 9 5 7 1 3 6] 112 | [7 6 3 4 1 8 2 5 9]]] (solve-board 3 [[N N N 2 6 N 7 N 1] 113 | [6 8 N N 7 N N 9 N] 114 | [1 9 N N N 4 5 N N] 115 | [8 2 N 1 N N N 4 N] 116 | [N N 4 6 N 2 9 N N] 117 | [N 5 N N N 3 N 2 8] 118 | [N N 9 3 N N N 7 4] 119 | [N 4 N N 5 N N 3 6] 120 | [7 N 3 N 1 8 N N N]]))) 121 | 122 | ;(it 123 | ; "can solve not fun problems" 124 | ; (should= [] (solve-board 3 [[N 2 N N N N N N N] 125 | ; [N N N 6 N N N N 3] 126 | ; [N 7 4 N 8 N N N N] 127 | ; [N N N N N 3 N N 2] 128 | ; [N 8 N N 4 N N 1 N] 129 | ; [6 N N 5 N N N N N] 130 | ; [N N N N 1 N 7 8 N] 131 | ; [5 N N N N 9 N N N] 132 | ; [N N N N N N N 4 N]]))) 133 | 134 | ) 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/sudoku/core.clj: -------------------------------------------------------------------------------- 1 | (ns sudoku.core 2 | (:require [clojure.set :as set])) 3 | 4 | (defn extract-row [row board] 5 | (board row)) 6 | 7 | (defn extract-column [column board] 8 | (map #(nth % column) board)) 9 | 10 | (defn extract-sector [rank [sector-column sector-row] board] 11 | (let [sector-rows (take rank (drop (* rank sector-row) board))] 12 | (flatten (map #(take rank (drop (* rank sector-column) %)) sector-rows)))) 13 | 14 | (defn find-missing-cells [rank board] 15 | (let [group-size (* rank rank) 16 | coords (for [col (range group-size) 17 | row (range group-size)] 18 | (if (nil? ((board row) col)) 19 | [col row] 20 | nil))] 21 | (filter some? coords))) 22 | 23 | (defn find-possible-values [rank [col row] board] 24 | (let [possible (set (drop 1 (range (inc (* rank rank))))) 25 | column-grouping (set (extract-column col board)) 26 | row-grouping (set (extract-row row board)) 27 | sector-col (quot col rank) 28 | sector-row (quot row rank) 29 | sector-grouping (set (extract-sector rank [sector-col sector-row] board))] 30 | (set/difference possible column-grouping row-grouping sector-grouping))) 31 | 32 | (defn set-cell [board [col row] value] 33 | (assoc-in board [row col] value) 34 | ) 35 | 36 | (declare solve-board-task) 37 | 38 | (defn try-possible-values [cell possible-values rank board] 39 | (let [grouped-solutions (for [value possible-values] 40 | (let [trial-board (set-cell board cell value)] 41 | (solve-board-task rank trial-board [])))] 42 | (apply concat grouped-solutions)) 43 | ) 44 | 45 | (defn solve-board-task [rank board solutions] 46 | (let [missing-cells (find-missing-cells rank board)] 47 | (if (empty? missing-cells) 48 | (conj solutions board) 49 | (let [first-missing-cell (first missing-cells) 50 | values (find-possible-values rank first-missing-cell board) 51 | new-solutions (try-possible-values first-missing-cell values rank board)] 52 | (concat solutions new-solutions))))) 53 | 54 | (defn solve-board [rank board] 55 | (solve-board-task rank board [])) 56 | --------------------------------------------------------------------------------