Skip to content

Commit 4757f0c

Browse files
author
Matthias Nannt
committed
add functions implementation, update readme
1 parent 86242cd commit 4757f0c

File tree

2 files changed

+149
-19
lines changed

2 files changed

+149
-19
lines changed

README.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33

44
# Question 1 (Naked Twins)
55
Q: How do we use constraint propagation to solve the naked twins problem?
6-
A: *Student should provide answer here*
6+
A: When we have a naked twin in a unit (row, col, ...), we know that the values for the twins
7+
can't be in other squares of this unit, so we delete the possibilities from these other squares. With this method
8+
we can reduce the complexity of our grid and continue with our algorithm (eliminate/only_choice and search)
79

810
# Question 2 (Diagonal Sudoku)
911
Q: How do we use constraint propagation to solve the diagonal sudoku problem?
10-
A: *Student should provide answer here*
12+
A: We take a look at the diagonals of the sudoku and eliminate the values of solved squares from their peers
13+
(other squares in the unit).
14+
With the only_choice method we take a look at the list of possible values for every square in the diagonals
15+
and see if there is one number that can only be placed in one square. If its the case we assign this number as the
16+
only solution for this square (and eliminate again).
1117

1218
### Install
1319

1420
This project requires **Python 3**.
1521

16-
We recommend students install [Anaconda](https://www.continuum.io/downloads), a pre-packaged Python distribution that contains all of the necessary libraries and software for this project.
22+
We recommend students install [Anaconda](https://www.continuum.io/downloads), a pre-packaged Python distribution that contains all of the necessary libraries and software for this project.
1723
Please try using the environment we provided in the Anaconda lesson of the Nanodegree.
1824

1925
##### Optional: Pygame
@@ -35,4 +41,4 @@ To visualize your solution, please only assign values to the values_dict using t
3541

3642
### Data
3743

38-
The data consists of a text file of diagonal sudokus for you to solve.
44+
The data consists of a text file of diagonal sudokus for you to solve.

solution.py

+139-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
# board setup
2+
3+
rows = 'ABCDEFGHI'
4+
cols = '123456789'
5+
6+
def cross(A, B):
7+
"Cross product of elements in A and elements in B."
8+
return [s+t for s in A for t in B]
9+
10+
boxes = cross(rows, cols)
11+
12+
row_units = [cross(r, cols) for r in rows]
13+
column_units = [cross(rows, c) for c in cols]
14+
square_units = [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')]
15+
diagonal_units = [[(rows[i] + cols[i]) for i in range(9)], [(rows[::-1][i] + cols[i]) for i in range(9)]]
16+
unitlist = row_units + column_units + square_units + diagonal_units
17+
units = dict((s, [u for u in unitlist if s in u]) for s in boxes)
18+
peers = dict((s, set(sum(units[s],[]))-set([s])) for s in boxes)
19+
20+
# board functionality
21+
122
assignments = []
223

324
def assign_value(values, box, value):
@@ -22,41 +43,137 @@ def naked_twins(values):
2243
# Find all instances of naked twins
2344
# Eliminate the naked twins as possibilities for their peers
2445

25-
def cross(A, B):
26-
"Cross product of elements in A and elements in B."
27-
pass
46+
for unit in unitlist:
47+
for box in unit:
48+
if len(values[box]) == 2:
49+
# check if there is a naked twin
50+
naked_twins = False
51+
other_boxes = unit.copy()
52+
other_boxes.remove(box)
53+
for other_box in other_boxes:
54+
if values[other_box] == values[box]:
55+
naked_twins = True
56+
# eliminate other occurences if naked twin is present
57+
if naked_twins:
58+
for other_box in other_boxes:
59+
if values[other_box] != values[box]:
60+
new_value = values[other_box].replace(values[box][0], '').replace(values[box][1], '')
61+
values = assign_value(values, other_box, new_value)
62+
63+
return values
64+
2865

2966
def grid_values(grid):
3067
"""
3168
Convert grid into a dict of {square: char} with '123456789' for empties.
32-
Args:
33-
grid(string) - A grid in string form.
34-
Returns:
35-
A grid in dictionary form
69+
Input: A grid in string form.
70+
Output: A grid in dictionary form
3671
Keys: The boxes, e.g., 'A1'
3772
Values: The value in each box, e.g., '8'. If the box has no value, then the value will be '123456789'.
3873
"""
39-
pass
74+
chars = []
75+
digits = '123456789'
76+
for c in grid:
77+
if c in digits:
78+
chars.append(c)
79+
if c == '.':
80+
chars.append(digits)
81+
assert len(chars) == 81
82+
return dict(zip(boxes, chars))
4083

4184
def display(values):
4285
"""
4386
Display the values as a 2-D grid.
44-
Args:
45-
values(dict): The sudoku in dictionary form
87+
Input: The sudoku in dictionary form
88+
Output: None
4689
"""
47-
pass
90+
width = 1+max(len(values[s]) for s in boxes)
91+
line = '+'.join(['-'*(width*3)]*3)
92+
for r in rows:
93+
print(''.join(values[r+c].center(width)+('|' if c in '36' else '')
94+
for c in cols))
95+
if r in 'CF': print(line)
96+
print
4897

4998
def eliminate(values):
50-
pass
99+
"""
100+
Go through all the boxes, and whenever there is a box with a value, eliminate this value from the values of all its peers.
101+
Input: A sudoku in dictionary form.
102+
Output: The resulting sudoku in dictionary form.
103+
"""
104+
for key, val in values.items():
105+
if len(val) == 1:
106+
for peer in peers[key]:
107+
if (isinstance(values[peer], str) and val in values[peer]):
108+
values = assign_value(values, peer, values[peer].replace(val, ""))
109+
return values
51110

52111
def only_choice(values):
53-
pass
112+
"""
113+
Go through all the units, and whenever there is a unit with a value that only fits in one box, assign the value to this box.
114+
Input: A sudoku in dictionary form.
115+
Output: The resulting sudoku in dictionary form.
116+
"""
117+
new_values = values.copy()
118+
119+
for unit in unitlist: # iterate over all units
120+
for box in unit: # check every box in the unit
121+
other_boxes = unit.copy()
122+
other_boxes.remove(box)
123+
if len(values[box]) > 1: # only check not already solved boxes
124+
for num in values[box]: # for every number in that box
125+
other_boxes_with_num = 0
126+
for other_box in other_boxes: # check occurences in other boxes
127+
if num in values[other_box]:
128+
other_boxes_with_num += 1
129+
if other_boxes_with_num == 0: # if number was in no other box, assign number
130+
new_values = assign_value(new_values, box, num)
131+
132+
return new_values
54133

55134
def reduce_puzzle(values):
56-
pass
135+
"""
136+
Iterate eliminate() and only_choice(). If at some point, there is a box with no available values, return False.
137+
If the sudoku is solved, return the sudoku.
138+
If after an iteration of both functions, the sudoku remains the same, return the sudoku.
139+
Input: A sudoku in dictionary form.
140+
Output: The resulting sudoku in dictionary form.
141+
"""
142+
solved_values = [box for box in values.keys() if len(values[box]) == 1]
143+
stalled = False
144+
while not stalled:
145+
solved_values_before = len([box for box in values.keys() if len(values[box]) == 1])
146+
values = eliminate(values)
147+
values = only_choice(values)
148+
solved_values_after = len([box for box in values.keys() if len(values[box]) == 1])
149+
stalled = solved_values_before == solved_values_after
150+
if len([box for box in values.keys() if len(values[box]) == 0]):
151+
return False
152+
return values
57153

58154
def search(values):
59-
pass
155+
"Using depth-first search and propagation, create a search tree and solve the sudoku."
156+
# First, reduce the puzzle using the previous function
157+
values = reduce_puzzle(values)
158+
159+
if not values:
160+
return False
161+
if all(len(values[s]) == 1 for s in boxes):
162+
return values ## Solved!
163+
164+
# Choose one of the unfilled squares with the fewest possibilities
165+
fewest_possibilities = {'square': 'A1', 'possibilites': 10}
166+
for key, val in values.items():
167+
if len(val) > 1 and len(val) < fewest_possibilities['possibilites']:
168+
fewest_possibilities = {'square': key, 'possibilites': len(val)}
169+
170+
# Now use recursion to solve each one of the resulting sudokus, and if one returns a value (not False), return that answer!
171+
for num in values[fewest_possibilities['square']]:
172+
new_values = values.copy()
173+
new_values[fewest_possibilities['square']] = num
174+
result = search(new_values)
175+
if result:
176+
return result
60177

61178
def solve(grid):
62179
"""
@@ -68,6 +185,13 @@ def solve(grid):
68185
The dictionary representation of the final sudoku grid. False if no solution exists.
69186
"""
70187

188+
values = grid_values(grid)
189+
result = search(values)
190+
return result
191+
192+
193+
194+
71195
if __name__ == '__main__':
72196
diag_sudoku_grid = '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3'
73197
display(solve(diag_sudoku_grid))

0 commit comments

Comments
 (0)