fun

Solving the sudoku takes an enourmous amount of time, especially on bigger sudokus with few hints. To resolve this, I did some code tuning. It works very simple: It first finds out what the possibilities are for each grid. In the previous version, the program would simply iterate over all numbers, which was a total waste of time. Now, it only tries the actual possibilities.

But it didn't stop there. I also added a new search algorithm, that finds the first empty grid, with the least amount of possibilities. That way, the grids with, for example, 2 possibilities, gets backtracked, even if there's an earlier grid with say, 10 possibilities. The result, is that the program now runs exponentially faster. Run some clock software or something on it if you will, but if you just try the 16x16 example, you can see it already.

Author
Vngngdn
Date
Aug. 28, 2016, 3:19 p.m.
Hash
ec0b82f601c829cf39a00c9f556a8594e12e3553
Parent
0e0070b075fc4533b50a8ef3b99ea8d2ee4ebdde
Modified file
sudoku-solver.py

sudoku-solver.py

130 additions and 10 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
9x9). So if you want hexadecimal plays, you can totally do that.
22
22
"""
23
23
24
24
# Imports:
25
25
from math import sqrt  # For the roots of the sudoku length.
26
26
27
27
# Constants:
28
28
EMPTY = 0
29
29
30
30
# Prints the given sudoku to the terminal in a readable way:
31
31
def print_sudoku(sudoku):
32
32
    # In order to print in a clean way, we first have to determine the length of
33
33
    # the biggest number:
34
34
    biggestNumber = 0
35
35
    for row in sudoku:
36
36
        for number in row:
37
37
            if number > biggestNumber:
38
38
                biggestNumber = number
39
39
40
40
    rootNumber = 10
41
41
    digits = 1
42
42
    while rootNumber**digits < biggestNumber:
43
43
        digits += 1
44
44
    # And now we've got the largest amount of digits.
45
45
46
46
    for row in sudoku:
47
47
        for number in row:
48
48
            # XXX: Even though the next line looks a bit dirty, it's a great way
49
49
            # to deduce the required amount of whitespace for readable output.
50
50
            # It takes the highest amount of digits, subtracted with the current
51
51
            # number's digits. so 10 in a sudoku with max. 3 digits: 3-2+1 = 2
52
52
            # whitespaces.
53
53
            spaces = " " * (digits-len(str(number)) + 1)
54
54
            print(str(number) + spaces, end="")
55
55
56
56
        for i in range(0, digits):
57
57
            # And after each row, a seperation whitespace.
58
58
            print()
59
59
60
60
# Given an empty sudoku, the solution is very simple.
+
61
+
62
+
63
# Given an empty sudoku, the solution is very simple.
61
64
# Although this is mainly a small optimization, as it is only usable when the
62
65
# root solution is possible.
63
66
def solve_empty_sudoku(sudoku): 
64
67
    n = sqrt(len(sudoku))
65
68
    
66
69
    for i in range(0, n*n):
67
70
        for j in range(0, n*n):
68
71
            sudoku[i][j] = (i*n + i/n + j) % (n*n) + 1;
69
72
    return sudoku
70
73
71
74
72
75
# Checks if the given sudoku qualifies as a root solution.
73
76
# This function mainly serves as a silly optimization to check beforehand
74
77
# whether we have to do the entire backtrack.
75
78
def is_possible_root_solution(sudoku):
76
79
    root_solution = solve_empty_sudoku()
77
80
    for x in range(0, len(root_solutxon)):
78
81
        for y in range(0, len(root_solution[x])):
79
82
            if sudoku[x][y] != root_solution[x][y] and sudoku[x][y] != EMPTY:
80
83
                return False
81
84
    # when here, all the sudoku can be filled as a root solution.
82
85
    return True
83
86
84
87
# Checks whether the given number already exists in the given array.
85
88
def exists_in_array(x, y, value, sudoku):
86
89
    if value == EMPTY:
87
90
        return False  # Just... of course.
88
91
89
92
    for i in range(0, len(sudoku)):
90
93
        if sudoku[i][y] == value:  # No need to check for empty
91
94
            return True
92
95
    return False
93
96
94
97
# Checks whether the number on the given location is unique in its column.
95
98
def exists_in_column(x, y, value, sudoku):
96
99
    if value == EMPTY:
97
100
        return False
98
101
99
102
    for i in range(0, len(sudoku[x])):
100
103
        if sudoku[x][i] == value:
101
104
            return True
102
105
    return False
103
106
104
107
# Checks whether the number on the given location is unique in its "root grid".
105
108
def exists_in_grid(x, y, value, sudoku):
106
109
    if value == EMPTY:
107
110
        return False
108
111
    
109
112
    # We're going to find out now in which part of the grid the (x,y) is put.
110
113
    # The idea I'm going for :
111
114
    # I'll first try to find out in which segment of the sudoku the value is in.
112
115
    # When I've found it, I trim the data to that segment, after I'll be
113
116
    # checking on that segment only.
114
117
    n = int(sqrt(len(sudoku)))  # Determining the square root of the sudoku's length.
115
118
116
119
    # The following algorithm is able to handle all square sudokus.
117
120
    A = x%n
118
121
    B = y%n
119
122
    for i in range(0, n):
120
123
        for j in range(0, n):
121
124
            C = x - A + i
122
125
            D = y - B + j
123
126
            if sudoku[C][D] == value and (C != x and D != y):
124
127
                return True
125
128
    return False
126
129
127
130
# Checks whether the sudoku still contains empty grids. Returns false if not,
128
131
# and vice versa.
129
132
def is_filled_sudoku(sudoku):
130
133
    for x in range(0, len(sudoku)):
131
134
        for y in range(0, len(sudoku[x])):
132
135
            if sudoku[x][y] == EMPTY:
133
136
                return False
134
137
    return True
135
138
136
139
# Looks from the upper left grid to the lower right grid of the sudoku to find
137
140
# an empty grid. If it encounters an empty grid, the respective (x,y)
138
141
# coordinates are returned.
139
142
# If no empty grid is being found, both returned values will be -1.
140
143
def find_first_empty_grid(sudoku):
141
144
    for x in range(0, len(sudoku)):
142
145
        for y in range(0, len(sudoku[x])):
143
146
            if sudoku[x][y] == EMPTY:
144
147
                return x, y
145
148
146
-
    # When we get to this point, there's no empty grid anymore in the sudoku.
147
149
    raise Exception("""
148
150
        The given sudoku does not feature any empty grids. Assert that you've
149
151
        given the sudoku to the is_filled_sudoku() function.
150
152
        """)
151
153
152
154
# Checks whether assigning the given value to the given coordinate in the sudoku
+
155
# smallest amount of possibilities. So grids with 2 possibilities are returned,
+
156
# even if an empty grid was found already, having 5 possibilities.
+
157
def find_first_empty_grid_optimized(sudoku, possibilities):
+
158
    i = 0
+
159
    j = 0
+
160
    minimum = len(sudoku)+1 # +1 guarantees 1 will always be chosen.
+
161
    for x in range(0, len(sudoku)):
+
162
        for y in range(0, len(sudoku[x])):
+
163
            if sudoku[x][y] == EMPTY:
+
164
                if len(possibilities[x][y]) < minimum:
+
165
                    i, j = x, y
+
166
                    minimum = len(possibilities[x][y])
+
167
    return i, j
+
168
+
169
+
170
+
171
# Checks whether assigning the given value to the given coordinate in the sudoku
153
172
# still renders the sudoku valid.
154
173
def is_valid_assignment(x, y, value, sudoku):
155
174
    if exists_in_array(x, y, value, sudoku):
156
175
        return False
157
176
    if exists_in_column(x, y, value, sudoku):
158
177
        return False
159
178
    if exists_in_grid(x, y, value, sudoku):
160
179
        return False
161
180
    return True
162
181
163
182
# Applies a recursive backtrack algorithm to the given sudoku, in an attempt to
+
183
def collect_possible_entries(sudoku, x, y):
+
184
    # The strategy is simple: Iterate over all possibilities, and check whether
+
185
    # it's possible or not.
+
186
    possible_values = set()
+
187
    for value in range(1, len(sudoku)+1):
+
188
        if is_valid_assignment(x, y, value, sudoku):
+
189
            possible_values.add(value)
+
190
    return possible_values
+
191
+
192
# Updates the possibilities, in function of the given position.
+
193
def remove_possibility(sudoku, possibilities, x, y):
+
194
    for i in range(len(possibilities)):
+
195
        for j in range(len(possibilities[i])):
+
196
            # i==x XOR j==y
+
197
            if (i==x and j!=y) or (i!=x and j==y):
+
198
                # The next if-test is necessary to avoid a KeyErrro.
+
199
                if sudoku[x][y] in possibilities[i][j]:
+
200
                    #assert (i != x and j == y) or (
+
201
                    possibilities[i][j].remove(sudoku[x][y])
+
202
    # Removal of possibilities in grid:
+
203
    n = int(sqrt(len(sudoku)))  # Determining the square root of the sudoku's length.
+
204
    A = x%n
+
205
    B = y%n
+
206
    for i in range(0, n):
+
207
        for j in range(0, n):
+
208
            C = x - A + i
+
209
            D = y - B + j
+
210
            if C!=x and D!=y:
+
211
                if sudoku[x][y] in possibilities[C][D]:
+
212
                    assert C != x and D != y
+
213
                    possibilities[C][D].remove(sudoku[x][y])
+
214
+
215
def add_possibility(sudoku, possibilities, x, y, value):
+
216
    for i in range(len(possibilities)):
+
217
        for j in range(len(possibilities[i])):
+
218
            # i==x XOR j==y
+
219
            #if (i==x or j==y) and not (i==x and j==y):
+
220
            if (i==x and j!=y) or (i!=x and j==y):
+
221
                #assert i != x and j != y
+
222
                possibilities[i][j].add(value)
+
223
    # Addition of possibilities in grid:
+
224
    n = int(sqrt(len(sudoku)))  # Determining the square root of the sudoku's length.
+
225
    A = x%n
+
226
    B = y%n
+
227
    for i in range(0, n):
+
228
        for j in range(0, n):
+
229
            C = x - A + i
+
230
            D = y - B + j
+
231
            if C!=x and D!=y:
+
232
                assert C != x and D != y
+
233
                possibilities[C][D].add(value)
+
234
+
235
# TODO: For remove/add_possibility: Add √n*√n grid removal as well.
+
236
+
237
+
238
# Applies a recursive backtrack algorithm to the given sudoku, in an attempt to
164
239
# solve it.
165
240
def recursive_solution(sudoku):
166
-
    if is_filled_sudoku(sudoku):
+
241
    if is_filled_sudoku(sudoku):
167
242
        return True  # The sudoku is solved.
168
243
    else:
169
244
        x, y = find_first_empty_grid(sudoku)
170
-
        for i in range(1, 1+len(sudoku)):
+
245
        x, y = find_first_empty_grid_optimized(sudoku, possibilities)
+
246
        if len(possibilities[x][y]) == EMPTY:
+
247
            # With an empty grid, there must be a possible value to enter. If there
+
248
            # isn't, that means that somewhere earlier, a possibility was removed,
+
249
            # because the wrong value was added. Thus, this is an impossible
+
250
            # solution, and we must backtrack.
+
251
            return False
+
252
+
253
        for i in range(1, 1+len(sudoku)):
171
254
            # We're going to fill in numbers from 1 to 9, to find a solution
172
-
            # that works.
173
-
            if is_valid_assignment(x, y, i, sudoku):
174
-
                sudoku[x][y] = i
+
255
            # We don't need to test if it's concerning a valid assignment, since
+
256
            # if it weren't a valid assignment, it wouldn't be listed as a
+
257
            # possibility in the first place.
+
258
                sudoku[x][y] = i
175
259
                if recursive_solution(sudoku) is False:
176
-
                    sudoku[x][y] = EMPTY
+
260
                possibilities[x][y].remove(i)
+
261
                if recursive_solution(sudoku, possibilities) is False:
+
262
                    sudoku[x][y] = EMPTY
177
263
                else:
+
264
                    i = 0 # Reloop over all posibilities (which may have updated
+
265
                    # Note how I don't add the tested possibility back, as I did
+
266
                    # remove the possibility. That is, because it's clear that
+
267
                    # this cannot be a solution if we want to solve the sudoku.
+
268
                    # Thus, it shouldn't be added back.
+
269
                else:
178
270
                    return True
179
271
        return False
180
272
181
273
# Assertion function. Checks whether the sudoku is a valid, and solved sudoku.
182
274
def test_solution(sudoku):
183
275
    discovered = []  # This list will be used to store discovered numbers.
184
276
    # Rows:
185
277
    for column in sudoku:
186
278
        for number in column:
187
279
            if number in discovered:
188
280
                return False
189
281
            else:
190
282
                discovered.append(number)
191
283
        discovered.clear()
192
284
193
285
    #Columns:
194
286
    for i in range(0, len(sudoku)):
195
287
        for y in range(0, len(sudoku[i])):
196
288
            if y in discovered:
197
289
                return False
198
290
            else:
199
291
                discovered.append(y)
200
292
        discovered.clear()
201
293
202
294
    #Grids:
203
295
    # Checking for grids requires us to collect the starting points of said
204
296
    # grids.
205
297
    n = int(sqrt(len(sudoku)))
206
298
    gridPoints = []
207
299
    for i in range(0, n):
208
300
        gridPoints.append(i*n)
209
301
210
302
    for k in gridPoints:
211
303
        for x in range(0,n):
212
304
            for y in range(0,n):
213
305
                if sudoku[x+k][y+k] in discovered:
214
306
                    return False
215
307
                else:
216
308
                    discovered.append(sudoku[x+k][y+k])
217
309
        discovered.clear()
218
310
219
311
    return True
220
312
221
313
# Prints an introduction paragraph to the user, explaining the details of the
222
314
# program, and how to operate it properly.
223
315
def print_introduction():
224
316
    introduction = """
225
317
        Welcome to Vngngdn's sudoku solver!
226
318
        I'll explain briefly what you have to do to use this program:
227
319
        After this paragraph, insert the first array of the sudoku, with each
228
320
        grid seperated by 1 space.
229
321
        If there are grids that are empty, use a 0 as placeholder for the empty
230
322
        space.
231
323
        When the row is complete, hit RETURN.
232
324
        The program will then ask for other arrays, until a square sudoku is
233
325
        formed.
234
326
        So, for example, if you enter 9 numbers, and hit RETURN, the program
235
327
        will ask for 8 more arrays, to create a 9x9 sudoku.
236
328
        When the last array has been entered, the program will stop asking for
237
329
        input, and immediately try to solve the sudoku.
238
330
        When the sudoku has been solved, it will print the solution in a
239
331
        readable way.
240
332
        If the sudoku could not be solved, it will print "Failed!", instead of a
241
333
        solution.
242
334
        This indicates the sudoku is most likely invalid, and can't be solved.
243
335
        Off you go now!
244
336
245
337
    """
246
338
    print(introduction)
247
339
248
340
# Asks the user for input.
249
341
def receive_input():
250
342
    sudoku = []  # This will be returned in the end.
251
343
    #integers = []  # A list which will be added to the sudoku after input.
252
344
    length = 1  # We know there will be at least 1 number.
253
345
    i = 0
254
346
    while i < length: 
255
347
        integers = []  # A list which will be added to the sudoku after input.
256
348
        integers.clear()
257
349
        # The amount of given numbers implies the remaining amount of rows,
258
350
        # because only square sudokus are handled.
259
351
        line = input()
260
352
        numbers = line.split()  # numbers now contains the given numbers.
261
353
        # XXX: Next addition might be redundant after the first one, but it's
262
354
        # still cleaner than having an entire redundant block.
263
355
        length = len(numbers)
264
356
        for number in numbers:
265
357
            integers.append(int(number))
266
358
        sudoku.append(integers)
267
359
         
268
360
        i += 1
269
361
270
362
    # Current state: The sudoku is completely filled in, including empty spots.
271
363
    # TODO: Add some defensive programming structure, to check for empty spots
272
364
    # in the sudoku. If so, ask the user where to insert the missing numbers.
273
365
    
274
366
    return sudoku
275
367
        
276
368
277
369
278
370
# MAIN (sort of)
279
371
280
372
print_introduction()
281
373
sudoku = receive_input()
282
374
"""
283
375
sudoku = [
284
376
        [0, 0, 0, 0, 9, 0, 4, 2, 0],
285
377
        [0, 0, 0, 0, 0, 0, 0, 0, 8],
286
378
        [9, 0, 0, 1, 0, 0, 3, 0, 0],
287
379
        [0, 3, 0, 0, 5, 8, 9, 1, 0],
288
380
        [0, 0, 0, 9, 0, 3, 0, 0, 0],
289
381
        [0, 1, 9, 4, 2, 0, 0, 5, 0],
290
382
        [0, 0, 5, 0, 0, 6, 0, 0, 4],
291
383
        [6, 0, 0, 0, 0, 0, 0, 0, 0],
292
384
        [0, 2, 7, 0, 8, 0, 0, 0, 0]
293
385
        ]
294
386
"""
295
387
"""
296
388
sudoku = [
297
389
        [2, 0, 0, 0],
298
390
        [0, 0, 1, 0],
299
391
        [0, 3, 0, 0],
300
392
        [0, 0, 0, 4]
301
393
        ]
302
394
"""
303
395
"""
304
-
sudoku = [
+
396
sudoku = [
305
397
        [0, 0, 0, 0, 1, 5, 8, 0, 6, 0, 0, 0, 7, 0, 0, 2],
306
398
        [1, 0, 0, 0, 4, 3, 0, 6, 0, 0, 10, 15, 0, 0, 0, 16],
307
399
        [0, 4, 8, 11, 0, 0, 10, 0, 0, 9, 0, 7, 0, 1, 0, 3],
308
400
        [9, 0, 5, 16, 2, 0, 0, 15, 0, 0, 8, 13, 10, 0, 0, 0],
309
401
        [0, 15, 0, 0, 0, 2, 0, 0, 0, 10, 0, 1, 4, 14, 6, 12],
310
402
        [0, 0, 12, 0, 5, 1, 0, 11, 14, 0, 0, 0, 8, 0, 7, 0],
311
403
        [0, 0, 0, 10, 0, 0, 6, 14, 0, 12, 0, 0, 0, 3, 0, 11],
312
404
        [13, 0, 7, 0, 0, 9, 0, 0, 0, 15, 0, 3, 0, 0, 16, 5],
313
405
        [15, 3, 0, 0, 11, 0, 12, 0, 0, 0, 1, 0, 0, 8, 0, 7],
314
406
        [5, 0, 10, 0, 0, 0, 1, 0, 7, 4, 0, 0, 15, 0, 0, 0],
315
407
        [0, 8, 0, 12, 0, 0, 0, 7, 16, 0, 11, 10, 0, 5, 0, 0],
316
408
        [7, 13, 16, 1, 3, 0, 5, 0, 0, 0, 15, 0, 0, 0, 11, 0],
317
409
        [0, 0, 0, 5, 9, 16, 0, 0, 10, 0, 0, 6, 11, 7, 0, 15],
318
410
        [10, 0, 15, 0, 8, 0, 2, 0, 0, 7, 0, 0, 14, 16, 1, 0],
319
411
        [8, 0, 0, 0, 7, 12, 0, 0, 9, 0, 5, 14, 0, 0, 0, 13],
320
412
        [14, 0, 0, 3, 0, 0, 0, 1, 0, 16, 13, 2, 0, 0, 0, 0]
321
413
        ]
322
414
"""
323
-
print("You entered the following sudoku:")
+
415
print("You entered the following sudoku:")
324
416
print_sudoku(sudoku)
325
417
326
418
if recursive_solution(sudoku):
327
-
    #if test_solution(sudoku):
+
419
possibilities = []
+
420
for x in range(len(sudoku)):
+
421
    row = []
+
422
    for y in range(len(sudoku[x])):
+
423
        entries = (collect_possible_entries(sudoku, x, y))
+
424
        row.append(entries)
+
425
        assert len(entries) != 0
+
426
    possibilities.append(row)
+
427
+
428
# We can already add trivial solutions:
+
429
"""print("Adding trivial solutions...")
+
430
solutions_count = 0
+
431
for x in range(len(sudoku)):
+
432
    for y in range(len(sudoku[x])):
+
433
        if len(possibilities[x][y]) == 1 and sudoku[x][y] == EMPTY:
+
434
            sudoku[x][y] = possibilities[x][y].pop()
+
435
            remove_possibility(sudoku, possibilities, x, y)
+
436
            solutions_count += 1
+
437
print("All trivial solutions added! ("+ str(solutions_count) +")")
+
438
print("Testing if still solveable...")
+
439
for x in range(len(sudoku)):
+
440
    for y in range(len(sudoku[x])):
+
441
        if sudoku[x][y] == EMPTY:
+
442
            assert len(possibilities[x][y]) != 0
+
443
print("Still solveable!")
+
444
"""
+
445
+
446
if recursive_solution(sudoku, possibilities):
+
447
    #if test_solution(sudoku):
328
448
    print("Sudoku solved!")
329
449
    print_sudoku(sudoku)
330
450
else:
331
451
    print("Failed!")
332
452
    print_sudoku(sudoku)
333
453
                
334
454