-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsudoku.py
222 lines (171 loc) · 6.2 KB
/
sudoku.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
from app import app
class Cell(object):
'''Represents one cell in the sudoku board'''
def __init__(self, row, col):
'''Create an empty cell'''
self.domain = list(range(1,10))
self.number = 0
self.row = row
self.col = col
@staticmethod
def fixedCell(num, row, col):
'''Create a cell with a fixed number.'''
cell = Cell(row, col)
cell.domain = [num]
cell.number = num
return cell
def __repr__(self):
return str(self.number)
class Board(object):
'''Represents a 9x9 sudoku board'''
def __init__(self, numbers):
'''
Create a new board.
arguments:
numbers -- a 9x9 2D list of ints. A zero refers to an empty cell.
'''
# A 2D (9x9) list of all cells on the board
self.cells = [[0 for j in range(9)] for i in range(9)]
# A map from square number (0-8) to all the cells in that square
self.squares = {i:[] for i in range(9)}
# The number of unassigned cells
self.numUnassigned = 0
# Populate self.cells and self.squares
for i in range(9):
for j in range(9):
# Add to self.cells
if numbers[i][j] > 0:
self.cells[i][j] = Cell.fixedCell(numbers[i][j], i, j)
else:
self.numUnassigned += 1
self.cells[i][j] = Cell(i, j)
# Add to self.squares
square = self.getSquare(i, j)
self.squares[square].append(self.cells[i][j])
def getSquare(self, row, col):
'''Returns the square that the cell at (row, column) resides at.'''
return ((row // 3) * 3) + (col // 3)
def solve(self):
'''
Solves the sodoku board using backtracking search.
returns -- True if successful, False otherwise
'''
if not self.initialFilter():
return False
return self.backtrackingSearch()
def backtrackingSearch(self):
'''
Performs backtracking search on the sudoku board.
returns -- True if successful, False otherwise
'''
# Base case: the board is solved
if self.numUnassigned == 0:
return True
# Get the cell with the fewest remaining values in its domain
cell = self.minimumRemainingValue()
# Loop over possible values in its domain
for num in cell.domain:
# Assign a valid number to the cell
cell.number = num
# Filter based on the assignment
filteredCells, result = self.filter(cell)
# If that assignment violates a contraint,
# undo the effects of the filter and skip it
if not result:
self.unfilter(num, filteredCells)
cell.number = 0
continue
# Recursively search
self.numUnassigned -= 1
result = self.backtrackingSearch()
if result: return True
# Remove assignment
cell.number = 0
self.numUnassigned += 1
# Undo the filtering
self.unfilter(num, filteredCells)
# The board in its current state is unsolvable. Backtrack!
return False
def initialFilter(self):
'''
Prunes the values of all cells' domains based on the fixed cells in the starting board.
returns: -- True if successful, False if not (ie, unsolvable puzzle)
'''
# Loop through every cell
for i in range(9):
for j in range(9):
cell = self.cells[i][j]
# If the cell has a fixed value, filter based on that cell
if cell.number != 0:
if not self.filter(cell)[1]:
# If that filtering violates a constraint, then the puzzle is unsolvable
return False
# Successfully filtered based on the starting board
return True
def filter(self, cell):
'''
Prunes the values of all cell's domains based on a number assignment to the argument cell.
returns -- (filteredCells, result)
filteredCells -- a list of all cells that had values removed from their domains
result -- a boolean indicating whether the filter was successful or not (violates constraints)
'''
# A list of cells whose domains have been filtered
filteredCells = []
# Loop through the cell's row
for currentCell in self.cells[cell.row]:
# If the cell is unassigned, remove cell.number from its domain
if currentCell.number == 0 and cell.number in currentCell.domain:
currentCell.domain.remove(cell.number)
filteredCells.append(currentCell)
# If we encounter another cell with the same number, then this assignment isn't correct
elif currentCell != cell and currentCell.number == cell.number:
return filteredCells, False
# Loop through the cell's column
for i in range(9):
currentCell = self.cells[i][cell.col]
# If the cell is unassigned, remove cell.number from its domain
if currentCell.number == 0 and cell.number in currentCell.domain:
currentCell.domain.remove(cell.number)
filteredCells.append(currentCell)
# If we encounter another cell with the same number, then this assignment isn't correct
elif currentCell != cell and currentCell.number == cell.number:
return filteredCells, False
# Loop through the cell's square
square = self.getSquare(cell.row, cell.col)
for currentCell in self.squares[square]:
# If the cell is unassigned, remove cell.number from its domain
if currentCell.number == 0 and cell.number in currentCell.domain:
currentCell.domain.remove(cell.number)
filteredCells.append(currentCell)
# If we encounter another cell with the same number, then this assignment isn't correct
elif currentCell != cell and currentCell.number == cell.number:
return filteredCells, False
return filteredCells, True
def unfilter(self, num, cells):
'''
Undoes the effects of filtering num from the domain of cells.
Called after backtracking to reset domains to their original state.
'''
# Loop through the filtered cells and add back the pruned number to their domain
for cell in cells:
cell.domain.append(num)
def minimumRemainingValue(self):
'''Returns the cell with the smallest domain.'''
# The cell with the smallest domain so far
smallestDomainCell = None
# The size of that domain
smallestDomain = 10
# Iterate through every cell
for i in range(9):
for j in range(9):
currentCell = self.cells[i][j]
lenDomain = len(currentCell.domain)
# Check that it is unassigned and its domain is smaller
if currentCell.number == 0 and lenDomain < smallestDomain:
# Return early because this is the best we can do
if lenDomain == 0:
return currentCell
# Overwrite with the new smallest cell
smallestDomainCell = currentCell
smallestDomain = lenDomain
return smallestDomainCell