-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSudokuSolver.py
324 lines (273 loc) · 13 KB
/
SudokuSolver.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# -*- coding: utf-8 -*-
"""
Created on Fri Feb 24 02:00:34 2017
@author: abhyudai
"""
import numpy as np
import Queue
class SudokuSolver:
def __init__(self):
self.NUM_OF_VARS = 81
self.var_domain = {} # dict of integer-> set() <=> variable -> domain
self.assignment = [None]*self.NUM_OF_VARS
self.number_of_guesses = 0
# initializes assignment and var_domain
def load_sudoku_data(self, puzzle_num):
fname = 'puz-'+ puzzle_num + '.txt'
with open(fname) as f:
content = f.readlines()
content = [x.strip() for x in content]
for i in range(0,len(content)):
line = content[i]
variables = line.split(' ')
for j in range(0,len(variables)):
value = variables[j]
self.assignment[9*i+j] = int(value) if value is not '-' else None
for i in range(0,len(self.assignment)):
var = self.assignment[i]
if var is None:
self.load_domain_values(i, self.assignment)
def reset_state(self):
self.number_of_guesses = 0
self.var_domain = {}
self.assignment = [None]*self.NUM_OF_VARS
#Entry point for search
def backtracking_search(self):
puzzle_num = ['001','002','010','015','025','026','048','051','062','076','081','082','090','095','099','100']
puzzle_index = 3
self.load_sudoku_data(puzzle_num[puzzle_index])
print "Initial Domain",self.var_domain
self.number_of_guesses = 0
solution = self.backtrack_recursive_with_inference(self.assignment)
print "Puzzle ",puzzle_num[puzzle_index], "\nSolution"
if solution is False:
print False
else:
print np.reshape(solution, (9,9))
print "Number of guesses AC3: ", self.number_of_guesses
# self.reset_state()
#
# self.load_sudoku_data(puzzle_num[puzzle_index])
# self.number_of_guesses = 0
# solution = self.backtrack_recursive(self.assignment)
# print "Puzzle ",puzzle_num[puzzle_index], "\nSolution"
# if solution is False:
# print False
# else:
# print np.reshape(solution, (9,9))
# print "Number of guesses", self.number_of_guesses
#Entry point for search
def backtracking_search_for_all(self):
puzzle_num = ['001','002','010','015','025','026','048','051','062','076','081','082','090','095','099','100']
for puzzle_index in range(0,len(puzzle_num)):
#puzzle_index = 4
self.load_sudoku_data(puzzle_num[puzzle_index])
solution = self.backtrack_recursive(self.assignment)
print "Puzzle ",puzzle_num[puzzle_index], "\nSolution"
if solution is False:
print False
else:
print np.reshape(solution, (9,9))
print "Number of guesses:", self.number_of_guesses
self.reset_state()
self.load_sudoku_data(puzzle_num[puzzle_index])
self.backtrack_recursive_mrv(self.assignment)
print "Number of guesses with MRV:", self.number_of_guesses
self.reset_state()
self.load_sudoku_data(puzzle_num[puzzle_index])
self.backtrack_recursive_with_inference(self.assignment)
print "Number of guesses with AC3:", self.number_of_guesses
self.reset_state()
#Recursive backtracking function
def backtrack_recursive(self, assignment):
if self.is_assignment_complete(assignment) is True:
return assignment
#Get next unassigned variable -either default or using mrv
var = self.select_unassigned_variable(assignment)
#Which value to assign to the variable first? - How to decide order?
values = self.order_domain_values(var,assignment)
if len(values)>0:
self.number_of_guesses += len(values) -1
for value in values:
if(self.is_consistent_assigment(value,var,assignment)):
assignment[var] = value
result = self.backtrack_recursive(assignment)
if(result is not False):
return result
assignment[var] = None
return False
#Recursive backtracking function
def backtrack_recursive_mrv(self, assignment):
if self.is_assignment_complete(assignment) is True:
return assignment
#Get next unassigned variable -either default or using mrv
var = self.select_unassigned_variable_mrv(assignment)
#Which value to assign to the variable first? - How to decide order?
values = self.order_domain_values(var,assignment)
if len(values)>0:
self.number_of_guesses += len(values) -1
for value in values:
if(self.is_consistent_assigment(value,var,assignment)):
assignment[var] = value
result = self.backtrack_recursive_mrv(assignment)
if(result is not False):
return result
assignment[var] = None
return False
#Recursive backtracking function
def backtrack_recursive_with_inference(self, assignment):
if self.is_assignment_complete(assignment) is True:
return assignment
#Get next unassigned variable -either default or using mrv
var = self.select_unassigned_variable(assignment)
#Which value to assign to the variable first? - How to decide order?
values = self.order_domain_values(var,assignment)
if len(values)>0:
self.number_of_guesses += len(values) -1
for value in values:
inferences = []
if(self.is_consistent_assigment(value,var,assignment)):
assignment[var] = value
inferences = self.get_inferences(var, value, assignment)
#print "Inferences",inferences
if(inferences is not False):
self.add_inferences_to_assignment(assignment, inferences)
#print np.reshape(assignment,(9,9))
result = self.backtrack_recursive_with_inference(assignment)
if(result is not False):
return result
self.remove_value_and_inferences_from_assignment(var, value, inferences, assignment)
return False
#returns the least constraining value for var - in this case does not matter, return first value
def order_domain_values(self, var, assignment):
if var in self.var_domain:
values = self.var_domain[var]
return sorted(list(values))
#Have all variables been assignned?
def is_assignment_complete(self, assignment):
if sum(x is None for x in assignment) is 0:
return True
return False
#Use MRV here
def select_unassigned_variable_mrv(self, assignment):
min_domain = 9
mrv_var = None
for k,v in self.var_domain.iteritems():
if(len(v)<=min_domain and assignment[k] is None):
mrv_var = k
min_domain = len(v)
if(min_domain is 1):
return mrv_var
return mrv_var
#returns the first unassigned variable
def select_unassigned_variable(self, assignment):
for k,v in self.var_domain.iteritems():
if assignment[k] is None:
return k
# Check if var can be assigned to variable - any constraint violated?
def is_consistent_assigment(self, value, var, assignment):
unavailable_values = self.get_unavailable_values(var, assignment)
return value not in unavailable_values
def load_domain_values(self, var, assignment):
unavailable_values = self.get_unavailable_values(var, assignment)
universal_set = set(np.arange(1,10))
self.var_domain[var] = universal_set.difference(unavailable_values)
# Returns all pre-existing values in the row, box and column of var
def get_unavailable_values(self, var, assignment):
(i,j) = var/9, var%9
row_values = [assignment[9*i+k] for k in range(0,9) ]
col_values = [assignment[j+9*k] for k in range(0,9)]
box_row, box_col = 3*int(i/3) , 3*int(j/3)
box_top_left = 9*box_row + box_col
box_values = []
for row in range(0,3):
for col in range(0,3):
box_values.append(assignment[box_top_left+col+9*row])
sets = [set(row_values), set(col_values), set(box_values)]
unavailable_values = set.union(*sets) #Note this has None too but none will never be assigned
if None in unavailable_values:
unavailable_values.remove(None)
return unavailable_values
def arc_consistency_ac3(self, assignment):
q = Queue.Queue()
#Initially all the arcs are in CSP
for var in range(0,len(assignment)):
value = assignment[var]
if value is None:
neighbors = self.get_neighbors(var, assignment)
for n in neighbors:
q.put((var,n))
#print "arc",var,'->',n
while not q.empty():
(var_i,var_j) = q.get()
if(self.revise(var_i,var_j)):
if(len(self.var_domain[var_i]) is 0):
return False
neighbors = self.get_neighbors(var_i, assignment)
for i in range(0,len(neighbors)):
var_k = neighbors[i]
if(var_k is not var_j):
q.put((var_k,var_i))
return True
# returns true if and only if we revise the domain of var_i
def revise(self, var_i, var_j):
revised= False
removed = set()
#print "Arc",var_i,"->",var_j
for d in self.var_domain[var_i]:
v = self.var_domain[var_j]
if d in self.var_domain[var_j]:
v = self.var_domain[var_j] - set([d])
if len(v) is 0:
#self.var_domain[var_i].remove(d)
#print "Arc",var_i,"->",var_j
#print var_j, "has no value in domain", self.var_domain[var_j] ,"except", d
removed.add(d)
revised = True
if(len(removed) >0):
self.var_domain[var_i] = self.var_domain[var_i] - removed
#print "Removed", removed , "from domain of", var_i,"=>",self.var_domain[var_i]
return revised
def get_neighbors(self, var, assignment):
(i,j) = var/9, var%9
row_vars = [9*i+k for k in range(0,9) if assignment[9*i+k] is None and (9*i+k) is not var]
col_vars = [j+9*k for k in range(0,9) if assignment[j+9*k] is None and (j+9*k) is not var]
box_row, box_col = 3*int(i/3) , 3*int(j/3)
box_top_left = 9*box_row + box_col
box_vars = []
for row in range(0,3):
for col in range(0,3):
if(assignment[box_top_left+col+9*row] is None and box_top_left+col+9*row is not var):
box_vars.append(box_top_left+col+9*row)
neighbors = row_vars
neighbors.extend(col_vars)
neighbors.extend(box_vars)
sorted_neighbours_list = sorted(list(set(neighbors)))
return sorted_neighbours_list
#Use different inference methods, e.g arc consistency - as of now update domain
def get_inferences(self, var, value, assignment):
result = self.arc_consistency_ac3(assignment)
if(result is False):
return result
inferences = []
for var in range(0,len(assignment)):
value = assignment[var]
if value is None and len(self.var_domain[var]) is 1:
inferred_value =list(self.var_domain[var])[0]
inferences.append((var,inferred_value))
return inferences
#Add the list of variable inferences, to assignment
def add_inferences_to_assignment(self, assignment, inferences):
#print "Adding inferences to assignment"
for p in range(0,len(inferences)):
(var,value) = inferences[p]
assignment[var] = value
def remove_value_and_inferences_from_assignment(self, var, value, inferences, assignment):
assignment[var] = None
if(inferences is not False):
for p in range(0,len(inferences)):
(var,value) = inferences[p]
assignment[var] = None
if __name__ == "__main__":
csp = SudokuSolver()
csp.backtracking_search_for_all()