Added an introduction to the program, removed the example sudoku, and an input handler for the sudoku solver.
- Author
- Vngngdn
- Date
- July 22, 2016, 4:16 p.m.
- Hash
- fcf40fb6d89e9688f5d9ebfb919c35d9cfc8c7e2
- Parent
- 306409c3d126fdff16a950a002086c145771267a
- Modified file
- sudoku-solver.py
sudoku-solver.py ¶
69 additions and 34 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 |
# I don't think I need to explain this, do I? Although I will explain how the |
18 |
- | # program works: |
19 |
- | # One is supposed to give an array of arrays, which will each contain a series |
20 |
- | # of integer values in range [1,9]. Some of them may be 0, these will then stand |
21 |
- | # for an empty value. |
22 |
- | # This program's goal is to remove all 0's (blank spaces) and fill them so, that |
23 |
- | # the sudoku is considered solved. After that, it prints the sudoku to the |
24 |
- | # terminal. |
25 |
- | |
+ |
18 |
This sudoku solver takes a recursive approach to solve a given sudoku. |
+ |
19 |
Although there are many different variations and types of sudokus, this program |
+ |
20 |
only handles NxN sudokus (i.e. squares with root-subgrids, like the most common |
+ |
21 |
3x3). So if you want hexadecimal plays, you can totally do that. |
+ |
22 |
""" |
+ |
23 |
|
26 |
24 |
# Prints the given sudoku to the terminal in a readable way: |
27 |
25 |
def print_sudoku(sudoku): |
28 |
26 |
for x in sudoku: |
29 |
27 |
#print(sudoku[x]) |
30 |
- | for y in x: |
31 |
28 |
print(str(y) + " ", end="") |
32 |
29 |
#print(str(sudoku[x][y]) + " ", end="") |
33 |
- | print() # Start a new line. |
34 |
30 |
|
35 |
31 |
# Given an empty sudoku, the solution is very simple. |
36 |
32 |
def solve_empty_sudoku(sudoku): |
37 |
33 |
n = 3 |
38 |
34 |
for i in range(0, n*n): |
39 |
35 |
for j in range(0, n*n): |
40 |
36 |
sudoku[i][j] = (i*n + i/n + j) % (n*n) + 1; |
41 |
37 |
return sudoku |
42 |
38 |
|
43 |
39 |
# Creates a table of 9x9 in the form of an array containing arrays. |
44 |
40 |
def create_sudoku(): |
+ |
41 |
def create_sudoku(): |
45 |
42 |
x = [] |
46 |
43 |
for i in range(0,9): |
47 |
44 |
x.append([0, 0, 0, 0, 0, 0, 0, 0, 0]) |
48 |
45 |
return x |
49 |
46 |
|
50 |
47 |
EMPTY = 0 |
51 |
48 |
|
+ |
49 |
|
52 |
50 |
# Checks if the given sudoku qualifies as a root solution. |
53 |
51 |
def is_possible_root_solution(sudoku): |
+ |
52 |
# whether we have to do the entire backtrack. |
+ |
53 |
def is_possible_root_solution(sudoku): |
54 |
54 |
root_solution = solve_empty_sudoku() |
55 |
55 |
for i in range(0, len(root_solution)): |
56 |
56 |
for j in range(0, len(root_solution[i])): |
57 |
57 |
if sudoku[i][j] != root_solution[i][j] and sudoku[i][j] != 0: |
58 |
58 |
return False |
59 |
59 |
# When here, all the sudoku can be filled as a root solution. |
60 |
60 |
return True |
61 |
61 |
|
62 |
62 |
# Checks whether the given number already exists in the given array. |
63 |
63 |
def exists_in_array(x, y, value, sudoku): |
64 |
64 |
#value = sudoku[x][y] |
65 |
- | for i in range(0, len(sudoku)): |
66 |
65 |
if sudoku[i][y] == value and sudoku[i][y] != EMPTY: |
67 |
66 |
return True |
68 |
67 |
return False |
69 |
68 |
|
70 |
69 |
# Checks whether the number on the given location is unique in its column. |
71 |
70 |
def exists_in_column(x, y, value, sudoku): |
72 |
71 |
#value = sudoku[x][y] |
73 |
- | for i in range(0, len(sudoku[x])): |
74 |
72 |
if sudoku[x][i] == value: |
75 |
73 |
return True |
76 |
74 |
return False |
77 |
75 |
|
78 |
76 |
# Checks whether the number on the given location is unique in its (3x3) grid. |
79 |
77 |
def exists_in_grid(x, y, value, sudoku): |
80 |
78 |
#value = sudoku[x][y] |
81 |
- | # We're going to find out now in which part of the grid the (x,y) is put. |
82 |
79 |
# We'll increment x and y by 1 in order to have some 1-indexed math: |
83 |
- | |
84 |
- | # The idea I'm going for now: |
85 |
80 |
# I'll first try to find out in which segment of the sudoku the value is in. |
86 |
81 |
# When I've found it, I trim the data to that segment, after I'll be |
87 |
82 |
# checking on that segment only. |
88 |
83 |
# Chart: |
89 |
84 |
""" |
90 |
- | +-----+ |
91 |
- | |A B C| |
92 |
- | |D E F| |
93 |
- | |G H I| |
94 |
- | +-----+ |
95 |
- | """ |
96 |
- | n = 3 |
+ |
85 |
n = 3 |
97 |
86 |
|
98 |
87 |
|
99 |
88 |
A = x%n |
100 |
89 |
B = y%n |
101 |
90 |
for i in range(0, n): |
102 |
91 |
for j in range(0, n): |
103 |
92 |
C = x - A + i |
104 |
93 |
D = y - B + j |
105 |
94 |
if sudoku[C][D] == value and (C != x and D != y): |
106 |
95 |
return True |
107 |
96 |
return False |
108 |
97 |
|
109 |
98 |
def is_filled_sudoku(sudoku): |
110 |
99 |
for i in range(0, len(sudoku)): |
111 |
100 |
for j in range(0, len(sudoku[i])): |
112 |
101 |
if sudoku[i][j] == 0: |
113 |
102 |
return False |
114 |
103 |
return True |
115 |
104 |
|
116 |
105 |
def find_first_empty_grid(sudoku): |
117 |
106 |
for i in range(0, len(sudoku)): |
118 |
107 |
for j in range(0, len(sudoku[i])): |
119 |
108 |
if sudoku[i][j] == 0: |
120 |
109 |
return i, j |
121 |
110 |
|
122 |
111 |
def is_valid_assignment(x, y, value, sudoku): |
123 |
112 |
#sudoku[x][y] = value |
124 |
113 |
if exists_in_array(x, y, value, sudoku): |
125 |
114 |
#print("Exists in array!") |
126 |
115 |
return False |
127 |
116 |
if exists_in_column(x, y, value, sudoku): |
128 |
117 |
#print("Exists in column!") |
129 |
118 |
return False |
130 |
119 |
if exists_in_grid(x, y, value, sudoku): |
131 |
120 |
#print("Exists in grid!") |
132 |
121 |
return False |
133 |
122 |
return True |
134 |
123 |
|
135 |
124 |
# Makes a deep copy of the given sudoku, and returns that copy. |
136 |
125 |
def make_copy(sudoku): |
137 |
126 |
newSudoku = [] |
138 |
127 |
for array in sudoku: |
139 |
128 |
newSudoku.append(array.copy()) |
140 |
129 |
return newSudoku |
141 |
130 |
|
142 |
131 |
|
143 |
132 |
sudokuA = create_sudoku() |
144 |
133 |
|
145 |
134 |
def recursive_solution(sudoku): |
146 |
135 |
if is_filled_sudoku(sudoku): |
147 |
136 |
return True # The sudoku is solved. |
148 |
137 |
else: |
149 |
138 |
x, y = find_first_empty_grid(sudoku) |
150 |
139 |
#print(str(x) + "-" + str(y)) |
151 |
140 |
for i in range(1, 10): |
152 |
141 |
# We're going to fill in numbers from 1 to 9, to find a solution |
153 |
142 |
# that works. |
154 |
143 |
if is_valid_assignment(x, y, i, sudoku): |
155 |
144 |
sudoku[x][y] = i |
156 |
145 |
#oldSudoku = make_copy(sudoku) |
157 |
146 |
if recursive_solution(sudoku) is False: |
158 |
147 |
sudoku[x][y] = EMPTY |
159 |
148 |
else: |
160 |
149 |
return True |
161 |
150 |
return False |
162 |
151 |
|
163 |
152 |
def test_solution(sudoku): |
164 |
153 |
discovered = [] |
165 |
154 |
# Rows: |
166 |
155 |
for column in sudoku: |
167 |
156 |
discovered.clear() |
168 |
157 |
for loc in column: |
169 |
158 |
if loc in discovered: |
170 |
159 |
return False |
171 |
160 |
else: |
172 |
161 |
discovered.append(loc) |
173 |
162 |
|
174 |
163 |
#Columns: |
175 |
164 |
for i in range(0, len(sudoku)): |
176 |
165 |
discovered.clear() |
177 |
166 |
for y in range(0, len(sudoku[i])): |
178 |
167 |
if y in discovered: |
179 |
168 |
return False |
180 |
169 |
else: |
181 |
170 |
discovered.append(y) |
182 |
171 |
|
183 |
172 |
discovered.clear() |
184 |
173 |
|
185 |
174 |
#Grids: |
186 |
175 |
for k in [0, 3, 6]: |
187 |
176 |
discovered.clear() |
188 |
177 |
for x in range(0,3): |
189 |
178 |
for y in range(0,3): |
190 |
179 |
if sudoku[x+k][y+k] in discovered: |
191 |
180 |
return False |
192 |
181 |
else: |
193 |
182 |
discovered.append(sudoku[x+k][y+k]) |
194 |
183 |
|
195 |
184 |
return True |
196 |
185 |
|
197 |
186 |
|
198 |
187 |
|
199 |
188 |
# MAIN |
200 |
- | sudokuA = [ |
201 |
- | [8, 4, 0, 0, 0, 0, 0, 0, 0], |
202 |
- | [0, 0, 5, 7, 0, 1, 0, 3, 4], |
203 |
- | [9, 0, 7, 0, 0, 0, 0, 0, 0], |
204 |
- | [5, 9, 0, 2, 0, 0, 0, 0, 0], |
205 |
- | [0, 0, 0, 3, 8, 6, 0, 0, 0], |
206 |
- | [0, 0, 0, 0, 0, 4, 0, 8, 2], |
207 |
- | [0, 0, 0, 0, 0, 0, 3, 0, 1], |
208 |
- | [7, 3, 0, 5, 0, 9, 6, 0, 0], |
209 |
- | [0, 0, 0, 0, 0, 0, 0, 7, 5] |
210 |
- | ] |
211 |
- | |
+ |
189 |
# Prints an introduction paragraph to the user, explaining the details of the |
+ |
190 |
# program, and how to operate it properly. |
+ |
191 |
def print_introduction(): |
+ |
192 |
introduction = """ |
+ |
193 |
Welcome to Vngngdn's sudoku solver! |
+ |
194 |
I'll explain briefly what you have to do to use this program: |
+ |
195 |
After this paragraph, insert the first array of the sudoku, with each |
+ |
196 |
grid seperated by 1 space. |
+ |
197 |
If there are grids that are empty, use a 0 as placeholder for the empty |
+ |
198 |
space. |
+ |
199 |
When the row is complete, hit RETURN. |
+ |
200 |
The program will then ask for other arrays, until a square sudoku is |
+ |
201 |
formed. |
+ |
202 |
So, for example, if you enter 9 numbers, and hit RETURN, the program |
+ |
203 |
will ask for 8 more arrays, to create a 9x9 sudoku. |
+ |
204 |
When the last array has been entered, the program will stop asking for |
+ |
205 |
input, and immediately try to solve the sudoku. |
+ |
206 |
When the sudoku has been solved, it will print the solution in a |
+ |
207 |
readable way. |
+ |
208 |
If the sudoku could not be solved, it will print "Failed!", instead of a |
+ |
209 |
solution. |
+ |
210 |
This indicates the sudoku is most likely invalid, and can't be solved. |
+ |
211 |
Off you go now! |
+ |
212 |
""" |
+ |
213 |
print(introduction) |
+ |
214 |
|
+ |
215 |
# Asks the user for input. |
+ |
216 |
def receive_input(): |
+ |
217 |
sudoku = [] # This will be returned in the end. |
+ |
218 |
integers = [] # A list which will be added to the sudoku after input. |
+ |
219 |
length = 1 # We know there will be at least 1 number. |
+ |
220 |
|
+ |
221 |
for i in range(0, length): |
+ |
222 |
integers.clear() |
+ |
223 |
# The amount of given numbers implies the remaining amount of rows, |
+ |
224 |
# because only square sudokus are handled. |
+ |
225 |
line = input() |
+ |
226 |
numbers = line.split() # numbers now contains the given numbers. |
+ |
227 |
# XXX: Next addition might be redundant after the first one, but it's |
+ |
228 |
# still cleaner than having an entire redundant block. |
+ |
229 |
length = len(numbers) |
+ |
230 |
for number in numbers: |
+ |
231 |
integers.append(int(number)) |
+ |
232 |
sudoku.append(integers) |
+ |
233 |
|
+ |
234 |
# Current state: The sudoku is completely filled in, including empty spots. |
+ |
235 |
# TODO: Add some defensive programming structure, to check for empty spots |
+ |
236 |
# in the sudoku. If so, ask the user where to insert the missing numbers. |
+ |
237 |
|
+ |
238 |
return sudoku |
+ |
239 |
|
+ |
240 |
|
+ |
241 |
|
+ |
242 |
# MAIN (sort of) |
+ |
243 |
|
+ |
244 |
print_introduction() |
+ |
245 |
sudoku = receive_input() |
+ |
246 |
|
212 |
247 |
if recursive_solution(sudokuA): |
213 |
248 |
if test_solution(sudokuA): |
214 |
249 |
print_sudoku(sudokuA) |
215 |
250 |
else: |
216 |
251 |
print("Failed!") |
217 |
252 |
print_sudoku(sudokuA) |
218 |
253 |
|
219 |
254 |