Sudoku solver: Biggest refactoring of the code, since it's now mostly finished. Added a lot of cleaner code, and extra comments. I may add some extra assertion code and docstrings in the future, as well as some defensive programming structures. But for now, this will do.
- Author
- Vngngdn
- Date
- July 22, 2016, 7:23 p.m.
- Hash
- 75a01245f2fc4d11735d7978a9d55b7fc4ddfaf4
- Parent
- fcf40fb6d89e9688f5d9ebfb919c35d9cfc8c7e2
- Modified file
- sudoku-solver.py
sudoku-solver.py ¶
103 additions and 70 deletions.
View changes Hide changes
1 |
1 |
sudoku-solver.py - A simple program that can solve any (solvable) sudoku puzzle. |
2 |
2 |
Copyright 2016 Maarten 'Vngngdn' Vangeneugden |
3 |
3 |
|
4 |
4 |
Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
5 |
you may not use this file except in compliance with the License. |
6 |
6 |
You may obtain a copy of the License at |
7 |
7 |
|
8 |
8 |
https://www.apache.org/licenses/LICENSE-2.0 |
9 |
9 |
|
10 |
10 |
Unless required by applicable law or agreed to in writing, software |
11 |
11 |
distributed under the License is distributed on an "AS IS" BASIS, |
12 |
12 |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
13 |
See the License for the specific language governing permissions and |
14 |
14 |
limitations under the License. |
15 |
15 |
""" |
16 |
16 |
|
17 |
17 |
""" |
18 |
18 |
This sudoku solver takes a recursive approach to solve a given sudoku. |
19 |
19 |
Although there are many different variations and types of sudokus, this program |
20 |
20 |
only handles NxN sudokus (i.e. squares with root-subgrids, like the most common |
21 |
21 |
3x3). So if you want hexadecimal plays, you can totally do that. |
22 |
22 |
""" |
23 |
23 |
|
24 |
24 |
# Prints the given sudoku to the terminal in a readable way: |
+ |
25 |
from math import sqrt # For the roots of the sudoku length. |
+ |
26 |
|
+ |
27 |
# Constants: |
+ |
28 |
EMPTY = 0 |
+ |
29 |
|
+ |
30 |
# Prints the given sudoku to the terminal in a readable way: |
25 |
31 |
def print_sudoku(sudoku): |
26 |
32 |
for x in sudoku: |
27 |
- | for y in x: |
28 |
- | print(str(y) + " ", end="") |
29 |
- | print() # Start a new line. |
30 |
- | |
+ |
33 |
# the biggest number: |
+ |
34 |
biggestNumber = 0 |
+ |
35 |
for row in sudoku: |
+ |
36 |
for number in row: |
+ |
37 |
if number > biggestNumber: |
+ |
38 |
biggestNumber = number |
+ |
39 |
|
+ |
40 |
rootNumber = 10 |
+ |
41 |
digits = 1 |
+ |
42 |
while rootNumber**digits < biggestNumber: |
+ |
43 |
digits += 1 |
+ |
44 |
# And now we've got the largest amount of digits. |
+ |
45 |
|
+ |
46 |
for row in sudoku: |
+ |
47 |
for number in sudoku: |
+ |
48 |
# XXX: Even though the next line looks a bit dirty, it's a great way |
+ |
49 |
# to deduce the required amount of whitespace for readable output. |
+ |
50 |
# It takes the highest amount of digits, subtracted with the current |
+ |
51 |
# number's digits. so 10 in a sudoku with max. 3 digits: 3-2+1 = 2 |
+ |
52 |
# whitespaces. |
+ |
53 |
spaces = " " * (digits-len(str(number)) + 1) |
+ |
54 |
print(str(number) + spaces, end="") |
+ |
55 |
|
+ |
56 |
for i in range(0, digits): |
+ |
57 |
# And after each row, a seperation whitespace. |
+ |
58 |
print() |
+ |
59 |
|
31 |
60 |
# Given an empty sudoku, the solution is very simple. |
32 |
61 |
def solve_empty_sudoku(sudoku): |
+ |
62 |
# root solution is possible. |
+ |
63 |
def solve_empty_sudoku(sudoku): |
33 |
64 |
n = 3 |
34 |
- | for i in range(0, n*n): |
+ |
65 |
|
+ |
66 |
for i in range(0, n*n): |
35 |
67 |
for j in range(0, n*n): |
36 |
68 |
sudoku[i][j] = (i*n + i/n + j) % (n*n) + 1; |
37 |
69 |
return sudoku |
38 |
70 |
|
39 |
71 |
# Creates a table of 9x9 in the form of an array containing arrays. |
40 |
- | """ |
41 |
- | def create_sudoku(): |
42 |
- | x = [] |
43 |
- | for i in range(0,9): |
44 |
- | x.append([0, 0, 0, 0, 0, 0, 0, 0, 0]) |
45 |
- | return x |
46 |
- | |
47 |
- | EMPTY = 0 |
48 |
- | """ |
49 |
- | |
50 |
72 |
# Checks if the given sudoku qualifies as a root solution. |
51 |
73 |
# This function mainly serves as a silly optimization to check beforehand |
52 |
74 |
# whether we have to do the entire backtrack. |
53 |
75 |
def is_possible_root_solution(sudoku): |
54 |
76 |
root_solution = solve_empty_sudoku() |
55 |
77 |
for i in range(0, len(root_solution)): |
56 |
- | for j in range(0, len(root_solution[i])): |
57 |
- | if sudoku[i][j] != root_solution[i][j] and sudoku[i][j] != 0: |
58 |
- | return False |
+ |
78 |
for y in range(0, len(root_solution[x])): |
+ |
79 |
if sudoku[x][y] != root_solution[x][y] and sudoku[x][y] != EMPTY: |
+ |
80 |
return False |
59 |
81 |
# When here, all the sudoku can be filled as a root solution. |
60 |
- | return True |
+ |
82 |
return True |
61 |
83 |
|
62 |
84 |
# Checks whether the given number already exists in the given array. |
63 |
85 |
def exists_in_array(x, y, value, sudoku): |
64 |
86 |
for i in range(0, len(sudoku)): |
+ |
87 |
return False # Just... of course. |
+ |
88 |
|
+ |
89 |
for i in range(0, len(sudoku)): |
65 |
90 |
if sudoku[i][y] == value and sudoku[i][y] != EMPTY: |
66 |
- | return True |
+ |
91 |
return True |
67 |
92 |
return False |
68 |
93 |
|
69 |
94 |
# Checks whether the number on the given location is unique in its column. |
70 |
95 |
def exists_in_column(x, y, value, sudoku): |
71 |
96 |
for i in range(0, len(sudoku[x])): |
+ |
97 |
return False |
+ |
98 |
|
+ |
99 |
for i in range(0, len(sudoku[x])): |
72 |
100 |
if sudoku[x][i] == value: |
73 |
101 |
return True |
74 |
102 |
return False |
75 |
103 |
|
76 |
104 |
# Checks whether the number on the given location is unique in its (3x3) grid. |
77 |
- | def exists_in_grid(x, y, value, sudoku): |
+ |
105 |
def exists_in_grid(x, y, value, sudoku): |
78 |
106 |
# We're going to find out now in which part of the grid the (x,y) is put. |
+ |
107 |
return False |
+ |
108 |
|
+ |
109 |
# We're going to find out now in which part of the grid the (x,y) is put. |
79 |
110 |
# The idea I'm going for now: |
80 |
- | # I'll first try to find out in which segment of the sudoku the value is in. |
+ |
111 |
# I'll first try to find out in which segment of the sudoku the value is in. |
81 |
112 |
# When I've found it, I trim the data to that segment, after I'll be |
82 |
113 |
# checking on that segment only. |
83 |
114 |
# Chart: |
84 |
- | n = len(sudoku) # Determining the size of the sudoku |
85 |
- | n = 3 |
86 |
- | |
87 |
- | |
+ |
115 |
|
88 |
116 |
A = x%n |
+ |
117 |
A = x%n |
89 |
118 |
B = y%n |
90 |
119 |
for i in range(0, n): |
91 |
120 |
for j in range(0, n): |
92 |
121 |
C = x - A + i |
93 |
122 |
D = y - B + j |
94 |
123 |
if sudoku[C][D] == value and (C != x and D != y): |
95 |
124 |
return True |
96 |
125 |
return False |
97 |
126 |
|
98 |
127 |
def is_filled_sudoku(sudoku): |
+ |
128 |
# and vice versa. |
+ |
129 |
def is_filled_sudoku(sudoku): |
99 |
130 |
for i in range(0, len(sudoku)): |
100 |
- | for j in range(0, len(sudoku[i])): |
101 |
- | if sudoku[i][j] == 0: |
102 |
- | return False |
+ |
131 |
for y in range(0, len(sudoku[x])): |
+ |
132 |
if sudoku[x][y] == EMPTY: |
+ |
133 |
return False |
103 |
134 |
return True |
104 |
135 |
|
105 |
136 |
def find_first_empty_grid(sudoku): |
+ |
137 |
# an empty grid. If it encounters an empty grid, the respective (x,y) |
+ |
138 |
# coordinates are returned. |
+ |
139 |
# If no empty grid is being found, both returned values will be -1. |
+ |
140 |
def find_first_empty_grid(sudoku): |
106 |
141 |
for i in range(0, len(sudoku)): |
107 |
- | for j in range(0, len(sudoku[i])): |
108 |
- | if sudoku[i][j] == 0: |
109 |
- | return i, j |
110 |
- | |
111 |
- | def is_valid_assignment(x, y, value, sudoku): |
+ |
142 |
for y in range(0, len(sudoku[x])): |
+ |
143 |
if sudoku[x][y] == EMPTY: |
+ |
144 |
return x, y |
+ |
145 |
|
+ |
146 |
# When we get to this point, there's no empty grid anymore in the sudoku. |
+ |
147 |
raise Exception(""" |
+ |
148 |
The given sudoku does not feature any empty grids. Assert that you've |
+ |
149 |
given the sudoku to the is_filled_sudoku() function. |
+ |
150 |
""") |
+ |
151 |
|
+ |
152 |
# Checks whether assigning the given value to the given coordinate in the sudoku |
+ |
153 |
# still renders the sudoku valid. |
+ |
154 |
def is_valid_assignment(x, y, value, sudoku): |
112 |
155 |
#sudoku[x][y] = value |
113 |
- | if exists_in_array(x, y, value, sudoku): |
114 |
156 |
#print("Exists in array!") |
115 |
- | return False |
116 |
157 |
if exists_in_column(x, y, value, sudoku): |
117 |
158 |
#print("Exists in column!") |
118 |
- | return False |
119 |
159 |
if exists_in_grid(x, y, value, sudoku): |
120 |
160 |
#print("Exists in grid!") |
121 |
- | return False |
122 |
161 |
return True |
123 |
162 |
|
124 |
163 |
# Makes a deep copy of the given sudoku, and returns that copy. |
125 |
- | def make_copy(sudoku): |
126 |
- | newSudoku = [] |
127 |
- | for array in sudoku: |
128 |
- | newSudoku.append(array.copy()) |
129 |
- | return newSudoku |
130 |
- | |
131 |
- | |
132 |
- | sudokuA = create_sudoku() |
133 |
- | |
134 |
- | def recursive_solution(sudoku): |
+ |
164 |
# solve it. |
+ |
165 |
def recursive_solution(sudoku): |
135 |
166 |
if is_filled_sudoku(sudoku): |
136 |
167 |
return True # The sudoku is solved. |
137 |
168 |
else: |
138 |
169 |
x, y = find_first_empty_grid(sudoku) |
139 |
170 |
#print(str(x) + "-" + str(y)) |
140 |
- | for i in range(1, 10): |
141 |
- | # We're going to fill in numbers from 1 to 9, to find a solution |
+ |
171 |
# We're going to fill in numbers from 1 to 9, to find a solution |
142 |
172 |
# that works. |
143 |
173 |
if is_valid_assignment(x, y, i, sudoku): |
144 |
174 |
sudoku[x][y] = i |
145 |
175 |
#oldSudoku = make_copy(sudoku) |
146 |
- | if recursive_solution(sudoku) is False: |
147 |
176 |
sudoku[x][y] = EMPTY |
148 |
177 |
else: |
149 |
178 |
return True |
150 |
179 |
return False |
151 |
180 |
|
152 |
181 |
def test_solution(sudoku): |
+ |
182 |
def test_solution(sudoku): |
153 |
183 |
discovered = [] |
154 |
- | # Rows: |
+ |
184 |
# Rows: |
155 |
185 |
for column in sudoku: |
156 |
186 |
discovered.clear() |
157 |
- | for loc in column: |
158 |
- | if loc in discovered: |
159 |
- | return False |
+ |
187 |
if number in discovered: |
+ |
188 |
return False |
160 |
189 |
else: |
161 |
190 |
discovered.append(loc) |
162 |
- | |
+ |
191 |
discovered.clear() |
+ |
192 |
|
163 |
193 |
#Columns: |
164 |
194 |
for i in range(0, len(sudoku)): |
165 |
195 |
discovered.clear() |
166 |
- | for y in range(0, len(sudoku[i])): |
167 |
196 |
if y in discovered: |
168 |
197 |
return False |
169 |
198 |
else: |
170 |
199 |
discovered.append(y) |
171 |
200 |
|
172 |
- | discovered.clear() |
173 |
- | |
+ |
201 |
|
174 |
202 |
#Grids: |
175 |
203 |
for k in [0, 3, 6]: |
176 |
- | discovered.clear() |
177 |
- | for x in range(0,3): |
178 |
- | for y in range(0,3): |
179 |
- | if sudoku[x+k][y+k] in discovered: |
+ |
204 |
# grids. |
+ |
205 |
n = sqrt(len(sudoku)) |
+ |
206 |
gridPoints = [] |
+ |
207 |
for i in range(0, n): |
+ |
208 |
gridPoints.append(i*n) |
+ |
209 |
|
+ |
210 |
for k in gridPoints: |
+ |
211 |
for x in range(0,n): |
+ |
212 |
for y in range(0,n): |
+ |
213 |
if sudoku[x+k][y+k] in discovered: |
180 |
214 |
return False |
181 |
215 |
else: |
182 |
216 |
discovered.append(sudoku[x+k][y+k]) |
183 |
217 |
|
+ |
218 |
|
184 |
219 |
return True |
185 |
220 |
|
186 |
221 |
|
187 |
- | |
188 |
- | |
189 |
- | # Prints an introduction paragraph to the user, explaining the details of the |
190 |
222 |
# program, and how to operate it properly. |
191 |
223 |
def print_introduction(): |
192 |
224 |
introduction = """ |
193 |
225 |
Welcome to Vngngdn's sudoku solver! |
194 |
226 |
I'll explain briefly what you have to do to use this program: |
195 |
227 |
After this paragraph, insert the first array of the sudoku, with each |
196 |
228 |
grid seperated by 1 space. |
197 |
229 |
If there are grids that are empty, use a 0 as placeholder for the empty |
198 |
230 |
space. |
199 |
231 |
When the row is complete, hit RETURN. |
200 |
232 |
The program will then ask for other arrays, until a square sudoku is |
201 |
233 |
formed. |
202 |
234 |
So, for example, if you enter 9 numbers, and hit RETURN, the program |
203 |
235 |
will ask for 8 more arrays, to create a 9x9 sudoku. |
204 |
236 |
When the last array has been entered, the program will stop asking for |
205 |
237 |
input, and immediately try to solve the sudoku. |
206 |
238 |
When the sudoku has been solved, it will print the solution in a |
207 |
239 |
readable way. |
208 |
240 |
If the sudoku could not be solved, it will print "Failed!", instead of a |
209 |
241 |
solution. |
210 |
242 |
This indicates the sudoku is most likely invalid, and can't be solved. |
211 |
243 |
Off you go now! |
212 |
244 |
""" |
+ |
245 |
""" |
213 |
246 |
print(introduction) |
214 |
247 |
|
215 |
248 |
# Asks the user for input. |
216 |
249 |
def receive_input(): |
217 |
250 |
sudoku = [] # This will be returned in the end. |
218 |
251 |
integers = [] # A list which will be added to the sudoku after input. |
219 |
252 |
length = 1 # We know there will be at least 1 number. |
220 |
253 |
|
221 |
254 |
for i in range(0, length): |
222 |
255 |
integers.clear() |
223 |
256 |
# The amount of given numbers implies the remaining amount of rows, |
224 |
257 |
# because only square sudokus are handled. |
225 |
258 |
line = input() |
226 |
259 |
numbers = line.split() # numbers now contains the given numbers. |
227 |
260 |
# XXX: Next addition might be redundant after the first one, but it's |
228 |
261 |
# still cleaner than having an entire redundant block. |
229 |
262 |
length = len(numbers) |
230 |
263 |
for number in numbers: |
231 |
264 |
integers.append(int(number)) |
232 |
265 |
sudoku.append(integers) |
233 |
266 |
|
234 |
267 |
# Current state: The sudoku is completely filled in, including empty spots. |
235 |
268 |
# TODO: Add some defensive programming structure, to check for empty spots |
236 |
269 |
# in the sudoku. If so, ask the user where to insert the missing numbers. |
237 |
270 |
|
238 |
271 |
return sudoku |
239 |
272 |
|
240 |
273 |
|
241 |
274 |
|
242 |
275 |
# MAIN (sort of) |
243 |
276 |
|
244 |
277 |
print_introduction() |
245 |
278 |
sudoku = receive_input() |
246 |
279 |
|
247 |
280 |
if recursive_solution(sudokuA): |
248 |
- | if test_solution(sudokuA): |
249 |
- | print_sudoku(sudokuA) |
250 |
- | else: |
+ |
281 |
if test_solution(sudoku): |
+ |
282 |
print_sudoku(sudoku) |
+ |
283 |
else: |
251 |
284 |
print("Failed!") |
252 |
285 |
print_sudoku(sudokuA) |
253 |
- | |
+ |
286 |
|
254 |
287 |