forked from mattvdk/udacity-AI-Sudoku-Solver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.py
187 lines (150 loc) · 5.52 KB
/
utils.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
from collections import defaultdict
rows = 'ABCDEFGHI'
cols = '123456789'
boxes = [r + c for r in rows for c in cols]
history = {} # history must be declared here so that it exists in the assign_values scope
def extract_units(unitlist, boxes):
"""Initialize a mapping from box names to the units that the boxes belong to
Parameters
----------
unitlist(list)
a list containing "units" (rows, columns, diagonals, etc.) of boxes
boxes(list)
a list of strings identifying each box on a sudoku board (e.g., "A1", "C7", etc.)
Returns
-------
dict
a dictionary with a key for each box (string) whose value is a list
containing the units that the box belongs to (i.e., the "member units")
"""
# the value for keys that aren't in the dictionary are initialized as an empty list
units = defaultdict(list)
for current_box in boxes:
for unit in unitlist:
if current_box in unit:
# defaultdict avoids this raising a KeyError when new keys are added
units[current_box].append(unit)
return units
def extract_peers(units, boxes):
"""Initialize a mapping from box names to a list of peer boxes (i.e., a flat list
of boxes that are in a unit together with the key box)
Parameters
----------
units(dict)
a dictionary with a key for each box (string) whose value is a list
containing the units that the box belongs to (i.e., the "member units")
boxes(list)
a list of strings identifying each box on a sudoku board (e.g., "A1", "C7", etc.)
Returns
-------
dict
a dictionary with a key for each box (string) whose value is a set
containing all boxes that are peers of the key box (boxes that are in a unit
together with the key box)
"""
# the value for keys that aren't in the dictionary are initialized as an empty list
peers = defaultdict(set) # set avoids duplicates
for key_box in boxes:
for unit in units[key_box]:
for peer_box in unit:
if peer_box != key_box:
# defaultdict avoids this raising a KeyError when new keys are added
peers[key_box].add(peer_box)
return peers
def assign_value(values, box, value):
"""You must use this function to update your values dictionary if you want to
try using the provided visualization tool. This function records each assignment
(in order) for later reconstruction.
Parameters
----------
values(dict)
a dictionary of the form {'box_name': '123456789', ...}
Returns
-------
dict
The values dictionary with the naked twins eliminated from peers
"""
# Don't waste memory appending actions that don't actually change any values
if values[box] == value:
return values
prev = values2grid(values)
values[box] = value
if len(value) == 1:
history[values2grid(values)] = (prev, (box, value))
return values
def cross(A, B):
"""Cross product of elements in A and elements in B """
return [x+y for x in A for y in B]
def values2grid(values):
"""Convert the dictionary board representation to as string
Parameters
----------
values(dict)
a dictionary of the form {'box_name': '123456789', ...}
Returns
-------
a string representing a sudoku grid.
Ex. '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3'
"""
res = []
for r in rows:
for c in cols:
v = values[r + c]
res.append(v if len(v) == 1 else '.')
return ''.join(res)
def grid2values(grid):
"""Convert grid into a dict of {square: char} with '123456789' for empties.
Parameters
----------
grid(string)
a string representing a sudoku grid.
Ex. '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3'
Returns
-------
A grid in dictionary form
Keys: The boxes, e.g., 'A1'
Values: The value in each box, e.g., '8'. If the box has no value,
then the value will be '123456789'.
"""
sudoku_grid = {}
for val, key in zip(grid, boxes):
if val == '.':
sudoku_grid[key] = '123456789'
else:
sudoku_grid[key] = val
return sudoku_grid
def display(values):
"""Display the values as a 2-D grid.
Parameters
----------
values(dict): The sudoku in dictionary form
"""
width = 1+max(len(values[s]) for s in boxes)
line = '+'.join(['-'*(width*3)]*3)
for r in rows:
print(''.join(values[r+c].center(width)+('|' if c in '36' else '')
for c in cols))
if r in 'CF': print(line)
print()
def reconstruct(values, history):
"""Returns the solution as a sequence of value assignments
Parameters
----------
values(dict)
a dictionary of the form {'box_name': '123456789', ...}
history(dict)
a dictionary of the form {key: (key, (box, value))} encoding a linked
list where each element points to the parent and identifies the value
assignment that connects from the parent to the current state
Returns
-------
list
a list of (box, value) assignments that can be applied in order to the
starting Sudoku puzzle to reach the solution
"""
path = []
prev = values2grid(values)
while prev in history:
prev, step = history[prev]
path.append(step)
return path[::-1]