-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsudoku_solver.py
131 lines (90 loc) · 3.6 KB
/
sudoku_solver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import copy
import math
class FaultySolution(Exception):
pass
class Sudoku:
class Node:
def __init__(self, sudoku, x, y):
self.sudoku = sudoku
self.x = x
self.y = y
self.value = None
self.possible_values = copy.copy(sudoku.myrange)
sqrt_n = round(math.sqrt(sudoku.n), 0)
i = int(x / sqrt_n)
j = int(y / sqrt_n)
self.group_index = (i, j)
sudoku.groups[(i, j)].append(self)
def __init__(self, n, myrange):
# n: number of rows/columns
# range: list of possible node values
self.n = n
self.n_empty_nodes = n*n
self.myrange = myrange
# Create groups
self.groups = dict()
sqrt_n = int(round(math.sqrt(n), 0))
for i in range(0, sqrt_n):
for j in range(0, sqrt_n):
self.groups[(i, j)] = []
indices = list(range(0, n))
self.xy_list = [(x, y) for x in indices for y in indices]
nodes = dict()
for (x, y) in self.xy_list:
nodes[(x, y)] = self.Node(self, x, y)
self.nodes = nodes
self.sorted_candidates = [nodes[k] for k in nodes.keys()]
self.sort_nodes()
def sort_nodes(self):
# Sort nodes by number of possible values. The nodes with smaller number of possible values should be tried
# first, so an ordering is needed.
candidates = copy.copy(self.sorted_candidates)
candidates = [c for c in candidates if c.possible_values]
self.sorted_candidates = sorted(candidates, key=lambda x: len(x.possible_values))
def set_value(self, x, y, value):
node = self.nodes[(x, y)]
if value not in node.possible_values:
raise FaultySolution
node.value = value
node.possible_values = []
self.n_empty_nodes -= 1
for new_x in range(0, self.n):
if self.nodes[(new_x, y)].possible_values:
self.remove_possibility(new_x, y, value)
for new_y in range(0, self.n):
if self.nodes[(x, new_y)].possible_values:
self.remove_possibility(x, new_y, value)
group_nodes = self.groups[node.group_index]
for gn in group_nodes:
if gn.possible_values and gn is not node:
self.remove_possibility(gn.x, gn.y, value)
self.sort_nodes()
return self
def remove_possibility(self, x, y, value):
node = self.nodes[(x, y)]
if value in node.possible_values:
node.possible_values.remove(value)
if len(node.possible_values) == 0:
raise FaultySolution
if len(node.possible_values) == 1:
self.set_value(x, y, node.possible_values[0])
class SudokuSolver:
def __init__(self):
pass
def __call__(self, sudoku, solutions):
if sudoku.n_empty_nodes == 0: # Apparently there is a solution.
print("Found a solution.")
solution = dict()
for i in range(0, sudoku.n):
for j in range(0, sudoku.n):
solution[(i, j)] = sudoku.nodes[(i, j)].value
solutions.append(solution)
else:
# Get top candidate and try all possible values in generator.
tc = sudoku.sorted_candidates[0]
solution_generator = (sudoku.set_value(tc.x, tc.y, pv) for pv in tc.possible_values)
try:
for possible_solution in solution_generator:
self.__call__(possible_solution, solutions)
except FaultySolution:
pass