Skip to content

Commit 4e9bcde

Browse files
author
Raphael Sofaer
committed
Finish solver
1 parent cb634af commit 4e9bcde

File tree

4 files changed

+188
-31
lines changed

4 files changed

+188
-31
lines changed

in.json

-1
This file was deleted.

src/sudoku/core.clj

+86-16
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,98 @@
44
clojure.contrib.combinatorics)
55
(:gen-class))
66

7-
(def input-data
8-
((json/decode (slurp "in.json")) :rows))
97
(def nums #{1 2 3 4 5 6 7 8 9})
108
(def pairs (cartesian-product (range 1 10) (range 1 10)))
9+
(defn print-return [a] (println a) a)
1110

12-
(defn get-row [mat i] (nth mat (- i 1)))
13-
(defn get-col [mat j] (map #(nth % (- j 1)) mat))
14-
(defn get-cell [mat i j] (nth (get-row mat i) (- j 1)))
15-
(defn get-zeros [mat] (map (filter #(= (first %) 0) (map (fn [[i j]] [(get-cell mat i j) [i j]]) pairs)))
11+
(defn get-row [mat i] (let [idx (* 9 (dec i))]
12+
(take 9 (drop idx mat))))
13+
(defn get-col [mat j] (take-nth 9 (drop (dec j) mat)))
14+
(defn get-cell [mat i j]
15+
(nth (get-row mat i) (dec j)))
16+
(defn get-zeros [mat] (map (filter #(= (first %) 0) (map (fn [[i j]] [(get-cell mat i j) [i j]]) pairs))))
1617

1718

18-
(defn block-n [i-or-j] (nth [1,1,1,2,2,2,3,3,3] (- i-or-j 1)))
19+
(defn block-n[i-or-j]
20+
(quot (- i-or-j 1) 3))
21+
1922
(defn get-box [mat i j]
2023
(let [i-block (block-n i)
21-
j-block (block-n j)
22-
rows (subvec mat (- (* i-block 3) 3) (* i-block 3))]
23-
(flatten
24-
(map (fn [row] (subvec row (- (* j-block 3) 3) (* j-block 3)) ) rows))))
25-
(defn nums-remaining [mat i j]
26-
(if (= (get-cell mat i j) 0)
27-
(difference nums (union (set (get-row mat i)) (set (get-col mat j)) (set (get-box mat i j))))
28-
#{}))
24+
j-block (block-n j)]
25+
(vec (flatten (nth (partition 3 (take-nth 3 (nthnext (partition 3 mat) j-block))) i-block )))))
26+
27+
(defn nums-remaining [mat idx]
28+
(let [i (inc (quot idx 9))
29+
j (inc (rem idx 9))]
30+
(if (= (get-cell mat i j) 0)
31+
(difference nums (union (set (get-row mat i)) (set (get-col mat j)) (set (get-box mat i j))))
32+
nil)
33+
))
34+
35+
36+
(defn consistent? [mat]
37+
(let [possibilities (map #(apply nums-remaining mat %) pairs)]
38+
(every? (fn [set] (or (nil? set) (> (count set) 0))) possibilities)))
39+
40+
(defn solved? [mat] (and (not (nil? mat)) (consistent? mat) (not-any? #(= % 0) mat)))
41+
42+
(defn set-at-index [v e idx]
43+
(concat (take idx v) [e] (drop (+ idx 1) v)))
44+
(defn first-zero [mat]
45+
(let [idx (.indexOf mat 0)]
46+
[(inc (quot idx 9)) (inc (rem idx 9)) idx]))
47+
48+
(defn solve [mat]
49+
(println mat)
50+
(let [idx (.indexOf mat 0)]
51+
(if (= -1 idx)
52+
mat
53+
(let [nums (nums-remaining mat idx)]
54+
(if (= 0 (count nums))
55+
nil
56+
(let [paths (filter #(not (nil? %))
57+
(map (fn [n] (solve (set-at-index mat n idx))) nums ))]
58+
(if (empty? paths)
59+
nil
60+
(first paths))))))))
61+
62+
(def easy-data
63+
[0 0 0 0 0 0 0 0 7
64+
7 0 4 0 0 0 8 9 3
65+
0 0 6 8 0 2 0 0 0
66+
67+
0 0 7 5 2 8 6 0 0
68+
0 8 0 0 0 6 7 0 1
69+
9 0 3 4 0 0 0 8 0
70+
71+
0 0 0 7 0 4 9 0 0
72+
6 0 0 0 9 0 0 0 0
73+
4 5 9 0 0 0 1 0 8])
74+
75+
(def hard-data
76+
[0 3 0 0 0 0 0 4 0
77+
0 1 0 0 9 7 0 5 0
78+
0 0 2 5 0 8 6 0 0
79+
80+
0 0 3 0 0 0 8 0 0
81+
9 0 0 0 0 4 3 0 0
82+
0 0 7 6 0 0 0 0 4
83+
84+
0 0 9 8 0 5 4 0 0
85+
0 7 0 0 0 0 0 2 0
86+
0 5 0 0 7 1 0 8 0])
2987

88+
(def ecco-data
89+
[7 0 0 0 0 0 0 6 4
90+
0 0 6 0 0 0 0 0 0
91+
0 0 0 0 0 8 0 2 0
92+
93+
5 6 3 0 0 0 0 0 0
94+
0 0 0 0 7 0 2 0 9
95+
0 0 0 0 0 0 0 0 0
96+
97+
0 5 0 0 0 0 3 0 0
98+
0 0 0 4 0 0 0 9 0
99+
1 7 0 9 0 0 0 0 8])
30100
(defn -main []
31-
(println (input-data)))
101+
(println (partition 3 (solve hard-data))))

sudoku.rb

+65-12
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@
22
require 'pp'
33
require 'ruby-debug'
44
require 'json'
5+
require 'perftools'
56
class SudokuState
67
def initialize rows
8+
#@@stop_after_a_while ||= 0
9+
@@failures ||= {}
10+
@nums_remaining ||= {}
711
@rows = rows
12+
@cols = []
813
end
914

1015
def row(i)
1116
@rows[i - 1]
1217
end
1318

1419
def col(j)
15-
@rows.map{|r| r[j-1]}
20+
@cols[j] ||= @rows.map{|r| r[j-1]}
1621
end
1722

1823
def cell(i, j)
1924
row(i)[j-1]
2025
end
2126

2227
def set_cell(i, j, n)
28+
@nums_remaining = {}
29+
@cols[j] = nil
2330
i = i-1
2431
j = j-1
2532
@rows[i][j] = n
@@ -41,11 +48,13 @@ def box(i, j)
4148
end
4249

4350
def nums_remaining(i, j)
44-
possibilities = [1,2,3,4,5,6,7,8,9]
45-
possibilities = possibilities - row(i)
46-
possibilities = possibilities - col(j)
47-
possibilities = possibilities - box(i,j)
48-
possibilities
51+
@nums_remaining[[i,j]] ||= lambda {
52+
possibilities = [1,2,3,4,5,6,7,8,9]
53+
possibilities = possibilities - row(i)
54+
possibilities = possibilities - col(j)
55+
possibilities = possibilities - box(i,j)
56+
possibilities
57+
}.call
4958
end
5059

5160
def zeros
@@ -60,21 +69,29 @@ def zeros
6069
result
6170
end
6271

72+
def inconsistent?
73+
return "inconsistent state" if @@failures[@rows.hash]
74+
if zeros.detect{|coords| nums_remaining(coords[0], coords[1]) == 0}
75+
@@failures[@rows.hash] = true
76+
return "inconsistent state"
77+
end
78+
end
6379
def basic_solve
6480
still_changing = true
6581
while still_changing
6682
still_changing = false
6783
zeros.each do |c|
6884
possibilities = nums_remaining(c[0],c[1])
6985
if possibilities.count == 0
86+
@@failures[@rows.hash] = true
7087
return "inconsistent state"
7188
elsif possibilities.count == 1
7289
still_changing = true
7390
set_cell(c[0], c[1], possibilities.first)
7491
end
7592
end
7693
end
77-
@rows
94+
self
7895
end
7996

8097
def solved?
@@ -85,13 +102,29 @@ def clone
85102
self.class.new JSON.parse(@rows.to_json)
86103
end
87104
def spec_solve
105+
#@@stop_after_a_while += 1
106+
#return if @@stop_after_a_while > 10000
88107
b_solved = self.clone.basic_solve
89-
if b_solved == "inconsistent state"
108+
if b_solved == "inconsistent state" || b_solved.inconsistent?
90109
return "inconsistent state"
91110
end
92-
state = SudokuState.new(b_solved)
93-
return state if state.solved?
94-
/*
111+
return b_solved if b_solved.solved?
112+
113+
stack = []
114+
b_solved.zeros.each do |zero|
115+
i = zero[0]
116+
j = zero[1]
117+
118+
v = nums_remaining(i,j)
119+
v.each do |num|
120+
new_state = self.clone
121+
new_state.set_cell(i, j, num)
122+
next if new_state.inconsistent?
123+
newer_state = new_state.spec_solve
124+
return newer_state if newer_state.instance_of?(SudokuState) && newer_state.solved?
125+
end
126+
end
127+
=begin
95128
Let R be the entries in b_solved having two or more possible values
96129
for each entry e in R
97130
let V be the possible values for E
@@ -105,7 +138,7 @@ def spec_solve
105138
pop state
106139
end
107140
end
108-
*/
141+
=end
109142
end
110143
end
111144
basic_solvable = [[0,0,0, 0,0,0, 0,0,7],
@@ -119,5 +152,25 @@ def spec_solve
119152
[0,0,0, 7,0,4, 9,0,0],
120153
[6,0,0, 0,9,0, 0,0,0],
121154
[4,5,9, 0,0,0, 1,0,8]]
155+
156+
adv_solvable = [[0,3,0, 0,0,0, 0,4,0],
157+
[0,1,0, 0,9,7, 0,5,0],
158+
[0,0,2, 5,0,8, 6,0,0],
159+
160+
[0,0,3, 0,0,0, 8,0,0],
161+
[9,0,0, 0,0,4, 3,0,0],
162+
[0,0,7, 6,0,0, 0,0,4],
163+
164+
[0,0,9, 8,0,5, 4,0,0],
165+
[0,7,0, 0,0,0, 0,2,0],
166+
[0,5,0, 0,7,1, 0,8,0]]
122167
s = SudokuState.new(basic_solvable)
123168
pp s.basic_solve
169+
170+
s = SudokuState.new(adv_solvable)
171+
172+
PerfTools::CpuProfiler.start("tmp/sudoku_profile") do
173+
solved = s.spec_solve
174+
end
175+
176+
pp solved

test/sudoku/test/core.clj

+37-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,40 @@
22
(:use [sudoku.core])
33
(:use [clojure.test]))
44

5-
(deftest replace-me ;; FIXME: write
6-
(is false "No tests have been written."))
5+
(deftest rows
6+
(is (= (get-row easy-data 1) [0 0 0 0 0 0 0 0 7]))
7+
(is (= (get-row easy-data 4) [0 0 7 5 2 8 6 0 0])))
8+
9+
(deftest columns
10+
(is (= (get-col easy-data 1) [0 7 0 0 0 9 0 6 4]))
11+
(is (= (get-col easy-data 4) [0 0 8 5 0 4 7 0 0])))
12+
13+
(deftest cells
14+
(is (= (get-cell easy-data 1 1) 0))
15+
(is (= (get-cell easy-data 9 9) 8))
16+
(is (= (get-cell easy-data 1 9) 7))
17+
(is (= (get-cell easy-data 9 1) 4))
18+
(is (= (get-cell easy-data 5 5) 0)))
19+
20+
(deftest boxes
21+
(is (= (get-box easy-data 1 1) [0 0 0 7 0 4 0 0 6]))
22+
(is (= (get-box easy-data 6 1) [0 0 7 0 8 0 9 0 3]))
23+
(is (= (get-box easy-data 7 7) [9 0 0 0 0 0 1 0 8]))
24+
(is (= (get-box easy-data 4 8) [6 0 0 7 0 1 0 8 0])))
25+
26+
(deftest zeros
27+
(is (= (apply get-cell easy-data (take 2(first-zero easy-data))) 0)))
28+
29+
(deftest solver
30+
(is (= (solve easy-data)
31+
[8 1 5 3 4 9 2 6 7
32+
7 2 4 6 5 1 8 9 3
33+
3 9 6 8 7 2 4 1 5
34+
35+
1 4 7 5 2 8 6 3 9
36+
5 8 2 9 3 6 7 4 1
37+
9 6 3 4 1 7 5 8 2
38+
39+
2 3 1 7 8 4 9 5 6
40+
6 7 8 1 9 5 3 2 4
41+
4 5 9 2 6 3 1 7 8])))

0 commit comments

Comments
 (0)