Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/games/sudoku_backtrack.pyx
4108 views
1
r"""
2
This module contains Cython code for a backtracking algorithm to solve Sudoku puzzles.
3
4
Once Cython implements closures and the ``yield`` keyword is possible, this can be moved into the ``sage.games.sudoku`` module, as part of the ``Sudoku.backtrack`` method, and this module can be banned.
5
"""
6
def backtrack_all(n, puzzle):
7
r"""
8
A routine to compute all the solutions to a Sudoku puzzle.
9
10
INPUT:
11
12
- ``n`` - the size of the puzzle, where the array is an `n^2\times n^2` grid
13
14
- ``puzzle`` - a list of the entries of the puzzle (1-based), in row-major order
15
16
OUTPUT:
17
18
A list of solutions, where each solution is a (1-based) list similar to ``puzzle``.
19
20
TEST:
21
22
This is just a cursory test here, since eventually this code will move.
23
See the `backtrack` method of the `Sudoku` class in the
24
`sage.games.sudoku` module for more enlightening examples. ::
25
26
sage: from sage.games.sudoku_backtrack import backtrack_all
27
sage: c = [0, 0, 0, 0, 1, 0, 9, 0, 0, 8, 0, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 3, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5, 8, 0, 6, 0, 0, 0, 0, 1, 3, 0, 7, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0]
28
sage: print backtrack_all(3, c)
29
[[6, 5, 4, 3, 1, 2, 9, 8, 7, 8, 3, 1, 4, 7, 9, 5, 2, 6, 2, 9, 7, 6, 8, 5, 4, 1, 3, 4, 7, 2, 5, 3, 8, 6, 9, 1, 3, 8, 5, 1, 9, 6, 2, 7, 4, 9, 1, 6, 7, 2, 4, 3, 5, 8, 5, 6, 8, 9, 4, 7, 1, 3, 2, 7, 4, 3, 2, 5, 1, 8, 6, 9, 1, 2, 9, 8, 6, 3, 7, 4, 5]]
30
31
ALGORITHM:
32
33
We traverse a search tree depth-first. Each level of the tree corresponds to a location in the grid, listed in row-major order. At each location we maintain a list of the symbols which may be used in that location as follows.
34
35
A location has "peers", which are the locations in the same row, column or box (sub-grid). As symbols are chosen (or fixed initially) at a location, they become ineligible for use at a peer. We track this in the ``available`` array where at each location each symbol has a count of how many times it has been made ineligible. As this counter transitions between 0 and 1, the number of eligible symbols at a location is tracked in the ``card`` array. When the number of eligible symbols at any location becomes 1, then we know that *must* be the symbol employed in that location. This then allows us to further update the eligible symbols at the peers of that location. When the number of the eligible symbols at any location becomes 0, then we know that we can prune the search tree.
36
37
So at each new level of the search tree, we propagate as many fixed symbols as we can, placing them into a two-ended queue (``fixed`` and ``fixed_symbol``) that we process until it is empty or we need to prune. All this recording of ineligible symbols and numbers of eligible symbols has to be unwound as we backup the tree, though.
38
39
The notion of propagating singleton cells forward comes from an essay by Peter Norvig [sudoku:norvig]_.
40
41
.. rubric:: Citations
42
43
.. [sudoku:norvig] Perter Norvig, "Solving Every Sudoku Puzzle", http://norvig.com/sudoku.html
44
"""
45
cdef:
46
# Arrays sizes are n^4, and n^2, with 3n^2-2n-1 for second slot of peers, n = 4
47
int i, j, count, level, apeer
48
int nsquare, npeers, nboxes
49
int grid_row, grid_col, grid_corner
50
int peers[256][39]
51
int box[256]
52
int available[256][16]
53
int card[256]
54
int hint, symbol, abox
55
int feasible
56
int nfixed[256]
57
int fixed[256][256]
58
int fixed_symbol[256][256]
59
60
int process, asymbol, alevel, process_level, process_symbol
61
62
# sanity check on size (types)
63
# n is "base" dimension
64
# nsquare is size of a grid
65
# nboxes is total number of entries
66
nsquare = n*n
67
nboxes = nsquare * nsquare
68
npeers = 3*n*n-2*n-1 # 2(n^2-1)+n^2-2n+1
69
70
# "Peers" of a box are boxes in the same column, row or grid
71
# Like the conflict graph when expressed as a graph coloring problem
72
for level in range(nboxes):
73
# location as row and column in square
74
# grids are numbered similarly, in row-major order
75
row = level // nsquare
76
col = level % nsquare
77
grid_corner = (row - (row % n))*nsquare + (col - (col % n))
78
grid_row = row // n
79
grid_col = col // n
80
count = -1
81
# Peers' levels in same grid, but not the box itself
82
for i in range(n):
83
for j in range(n):
84
grid_level = grid_corner + i*nsquare + j
85
if grid_level != level:
86
count += 1
87
peers[level][count] = grid_level
88
# Peers' levels in the same row, but not in grid
89
for i in range(nsquare):
90
if (i // 3 != grid_col):
91
count += 1
92
peers[level][count] = row*nsquare + i
93
# Peers' levels in same column, but not in grid
94
for i in range(nsquare):
95
if (i // 3 != grid_row):
96
count += 1
97
peers[level][count] = col + i*nsquare
98
99
# Initialize data structures
100
# Make every symbol available initially for a box
101
# And make set cardinality the size of symbol set
102
for level in range(nboxes):
103
box[level] = -1
104
card[level] = nsquare
105
for j in range(nsquare):
106
available[level][j] = 0
107
108
# For non-zero entries of input puzzle
109
# (1) Convert to zero-based indexing
110
# (2) Make a set of size 1 available initially
111
for level in range(nboxes):
112
# location as row and column in square
113
# grids are numbered similarly, in row-major order
114
hint = puzzle[level] - 1
115
if hint != -1:
116
# Limit symbol set at the hint's location to a singleton
117
for j in range(nsquare):
118
available[level][j] = 1
119
available[level][hint] = 0
120
card[level] = 1
121
# Remove hint from all peers' available symbols
122
# Track cardinality as sets adjust
123
for i in range(npeers):
124
apeer = peers[level][i]
125
available[apeer][hint] += 1
126
if available[apeer][hint] == 1:
127
card[apeer] -= 1
128
129
# Start backtracking
130
solutions = []
131
level = 0
132
box[level] = -1
133
while (level > -1):
134
symbol = box[level]
135
if (symbol != -1):
136
# restore symbols to peers
137
for i in range(nfixed[level]):
138
alevel = fixed[level][i]
139
asymbol = fixed_symbol[level][i]
140
for j in range(npeers):
141
abox = peers[alevel][j]
142
available[abox][asymbol] -= 1
143
if available[abox][asymbol] == 0:
144
card[abox] += 1
145
# move sideways in search tree to next available symbol
146
symbol += 1
147
while (symbol < nsquare) and (available[level][symbol] != 0):
148
symbol += 1
149
if symbol == nsquare:
150
# fell off the end sideways, backup
151
level = level - 1
152
else:
153
box[level] = symbol
154
# Remove elements of sets, adjust cardinalities
155
# Descend in search tree if no empty sets created
156
# Can't break early at an empty set
157
# or we will confuse restore that happens immediately
158
feasible = True
159
fixed[level][0] = level
160
fixed_symbol[level][0] = symbol
161
count = 0
162
process = -1
163
while (process < count) and feasible:
164
process += 1
165
process_level = fixed[level][process]
166
process_symbol = fixed_symbol[level][process]
167
for i in range(npeers):
168
abox = peers[process_level][i]
169
available[abox][process_symbol] += 1
170
if available[abox][process_symbol] == 1:
171
card[abox] -= 1
172
if card[abox] == 0:
173
feasible = False
174
if card[abox] == 1:
175
count += 1
176
fixed[level][count] = abox
177
asymbol = 0
178
while (available[abox][asymbol] != 0):
179
asymbol += 1
180
fixed_symbol[level][count] = asymbol
181
nfixed[level] = process+1
182
if feasible:
183
if level == nboxes - 1:
184
# Have a solution to save, stay at this bottom-most level
185
# Once Cython implements closures, a yield can go here
186
solutions.append([box[i]+1 for i in range(nboxes)])
187
else:
188
level = level + 1
189
box[level] = -1
190
return solutions
191