Finish hole digging
parent
6137158ea8
commit
1e2c2dcc25
|
@ -7,135 +7,32 @@ Credits for Generator: http://zhangroup.aporc.org/images/files/Paper_3485.pdf
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
#from . import Sudoku_Solver as solver
|
||||||
|
import Sudoku_Solver as solver
|
||||||
|
|
||||||
filledcell = re.compile('(?!0)')
|
filledcell = re.compile('(?!0)')
|
||||||
|
|
||||||
def cross(array1, array2):
|
def check_for_nonzeros(seq):
|
||||||
"""Cross product of elements in A and elements in B."""
|
return len([m.start() for m in filledcell.finditer(seq)])
|
||||||
return [a+b for a in array1 for b in array2]
|
|
||||||
|
|
||||||
|
|
||||||
digits = '123456789'
|
def generate_completed_grid(n):
|
||||||
rows = 'ABCDEFGHI'
|
|
||||||
cols = digits
|
|
||||||
squares = cross(rows, cols)
|
|
||||||
unitlist = ([cross(rows, c) for c in cols] +
|
|
||||||
[cross(r, cols) for r in rows] +
|
|
||||||
[cross(rs, cs) for rs in ('ABC', 'DEF', 'GHI') for cs in ('123','456','789')])
|
|
||||||
units = dict((s, [u for u in unitlist if s in u])
|
|
||||||
for s in squares)
|
|
||||||
peers = dict((s, set(sum(units[s], []))-set([s]))
|
|
||||||
for s in squares)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_grid(grid):
|
|
||||||
"""Convert grid to a dict of possible values, {square: digits}, or
|
|
||||||
return False if a contradiction is detected."""
|
|
||||||
# To start, every square can be any digit; then assign values from the grid.
|
|
||||||
values = dict((s, digits) for s in squares)
|
|
||||||
for s, d in grid_values(grid).items():
|
|
||||||
if d in digits and not assign(values, s, d):
|
|
||||||
return False # (Fail if we can't assign d to square s.)
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def grid_values(grid):
|
|
||||||
"""Convert grid into a dict of {square: char} with '0' or '.' for empties."""
|
|
||||||
chars = [c for c in grid if c in digits or c in '0.']
|
|
||||||
assert len(chars) == 81
|
|
||||||
return dict(zip(squares, chars))
|
|
||||||
|
|
||||||
|
|
||||||
def display(values):
|
|
||||||
"""Display these values as a 2-D grid."""
|
|
||||||
width = 1+max(len(values[s]) for s in squares)
|
|
||||||
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 assign(values, s, d):
|
|
||||||
"""Eliminate all the other values (except d) from values[s] and propagate.
|
|
||||||
Return values, except return False if a contradiction is detected."""
|
|
||||||
other_values = values[s].replace(str(d), '')
|
|
||||||
if all(eliminate(values, s, d2) for d2 in other_values):
|
|
||||||
return values
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def eliminate(values, s, d):
|
|
||||||
"""Eliminate d from values[s]; propagate when values or places <= 2.
|
|
||||||
Return values, except return False if a contradiction is detected."""
|
|
||||||
if d not in values[s]:
|
|
||||||
return values # Already eliminated
|
|
||||||
values[s] = values[s].replace(d, '')
|
|
||||||
# (1) If a square s is reduced to one value d2, then eliminate d2 from the peers.
|
|
||||||
if len(values[s]) == 0:
|
|
||||||
return False # Contradiction: removed last value
|
|
||||||
elif len(values[s]) == 1:
|
|
||||||
d2 = values[s]
|
|
||||||
if not all(eliminate(values, s2, d2) for s2 in peers[s]):
|
|
||||||
return False
|
|
||||||
# (2) If a unit u is reduced to only one place for a value d, then put it there.
|
|
||||||
for u in units[s]:
|
|
||||||
dplaces = [s for s in u if d in values[s]]
|
|
||||||
if len(dplaces) == 0:
|
|
||||||
return False # Contradiction: no place for this value
|
|
||||||
elif len(dplaces) == 1:
|
|
||||||
# d can only be in one place in unit; assign it there
|
|
||||||
if not assign(values, dplaces[0], d):
|
|
||||||
return False
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
#def solve(grid): return search(parse_grid(grid))
|
|
||||||
def solve(values): return search(values)
|
|
||||||
|
|
||||||
|
|
||||||
def search(values):
|
|
||||||
"""Using depth-first search and propagation, try all possible values."""
|
|
||||||
if values is False:
|
|
||||||
return False # Failed earlier
|
|
||||||
if all(len(values[s]) == 1 for s in squares):
|
|
||||||
return values # Solved!
|
|
||||||
# Chose the unfilled square s with the fewest possibilities
|
|
||||||
n, s = min((len(values[s]), s) for s in squares if len(values[s]) > 1)
|
|
||||||
return some(search(assign(values.copy(), s, d))
|
|
||||||
for d in values[s])
|
|
||||||
|
|
||||||
|
|
||||||
def some(seq):
|
|
||||||
"""Return some element of seq that is true."""
|
|
||||||
for e in seq:
|
|
||||||
if e:
|
|
||||||
return e
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def las_vegas(n):
|
|
||||||
# Generate a board by randomly picking n cells and
|
# Generate a board by randomly picking n cells and
|
||||||
# fill them a random digit from 1-9
|
# fill them a random digit from 1-9
|
||||||
values = parse_grid('0' * 81)
|
values = solver.parse_grid('0' * 81)
|
||||||
valid_assignments = 0
|
valid_assignments = 0
|
||||||
while valid_assignments < n:
|
while valid_assignments < n:
|
||||||
#display(values)
|
# display(values)
|
||||||
cell_to_assign = squares[random.randint(0, 80)]
|
cell_to_assign = solver.squares[random.randint(0, 80)]
|
||||||
valid_values = values[cell_to_assign]
|
valid_values = values[cell_to_assign]
|
||||||
if len(valid_values):
|
if len(valid_values):
|
||||||
value_to_assign = valid_values[random.randint(0, len(valid_values)-1)]
|
value_to_assign = valid_values[random.randint(0, len(valid_values) - 1)]
|
||||||
assign(values, cell_to_assign, value_to_assign)
|
solver.assign(values, cell_to_assign, value_to_assign)
|
||||||
valid_assignments += 1
|
valid_assignments += 1
|
||||||
return values
|
|
||||||
|
|
||||||
|
complete_values = solver.solve(values)
|
||||||
def generate_completed_grid():
|
|
||||||
complete_values = solve(las_vegas(11))
|
|
||||||
grid = ''
|
grid = ''
|
||||||
for s in squares:
|
for s in solver.squares:
|
||||||
grid += complete_values[s]
|
grid += complete_values[s]
|
||||||
|
|
||||||
return grid
|
return grid
|
||||||
|
@ -146,7 +43,7 @@ def generate_dig_sequence(difficulty):
|
||||||
if difficulty <= 1:
|
if difficulty <= 1:
|
||||||
random_number = list(range(81))
|
random_number = list(range(81))
|
||||||
while len(random_number) > 0:
|
while len(random_number) > 0:
|
||||||
print(len(random_number))
|
#print(len(random_number))
|
||||||
yield random_number.pop(random.randint(0, len(random_number)-1))
|
yield random_number.pop(random.randint(0, len(random_number)-1))
|
||||||
elif difficulty == 2:
|
elif difficulty == 2:
|
||||||
current = 0
|
current = 0
|
||||||
|
@ -172,9 +69,7 @@ def generate_dig_sequence(difficulty):
|
||||||
yield current
|
yield current
|
||||||
current += 1
|
current += 1
|
||||||
|
|
||||||
def generate_sudoku_puzzle(difficulty):
|
def specify_grid_properties(difficulty):
|
||||||
grid = generate_completed_grid()
|
|
||||||
|
|
||||||
if difficulty == 0:
|
if difficulty == 0:
|
||||||
n_givens = random.randint(50, 60)
|
n_givens = random.randint(50, 60)
|
||||||
lower_bound = 5
|
lower_bound = 5
|
||||||
|
@ -191,25 +86,41 @@ def generate_sudoku_puzzle(difficulty):
|
||||||
n_givens = random.randint(22, 27)
|
n_givens = random.randint(22, 27)
|
||||||
lower_bound = 0
|
lower_bound = 0
|
||||||
|
|
||||||
|
return n_givens, lower_bound
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sudoku_puzzle(difficulty):
|
||||||
|
grid = generate_completed_grid(11)
|
||||||
|
n_givens, lower_bound = specify_grid_properties()
|
||||||
dig_sequence = generate_dig_sequence(difficulty)
|
dig_sequence = generate_dig_sequence(difficulty)
|
||||||
holes = 0
|
holes = 0
|
||||||
|
|
||||||
while holes<81-n_givens:
|
while holes < 81-n_givens:
|
||||||
try:
|
try:
|
||||||
i = next(dig_sequence)
|
i = next(dig_sequence)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
print("Reach end of Sequence")
|
print("Reach end of Sequence")
|
||||||
break
|
break
|
||||||
# TODO: Check if givens at current row and column is at lower bound
|
row = i % 9
|
||||||
|
if check_for_nonzeros(grid[row:row+9]) > lower_bound:
|
||||||
# TODO: Dig the current hole and check for uniqueness
|
current_number = grid[i]
|
||||||
|
other_numbers = solver.digits.replace(current_number, '')
|
||||||
|
unique = True
|
||||||
|
for digit in other_numbers:
|
||||||
|
grid_check = grid[:i] + digit + grid[i+1:]
|
||||||
|
if solver.solve(solver.parse_grid(grid_check)):
|
||||||
|
unique = False
|
||||||
|
break
|
||||||
|
if unique:
|
||||||
|
grid = grid[:i] + '0' + grid[i+1:]
|
||||||
|
holes += 1
|
||||||
# TODO: Propagate and Output
|
# TODO: Propagate and Output
|
||||||
return grid
|
return grid
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#print(generate_completed_grid())
|
puzzle = generate_sudoku_puzzle(4)
|
||||||
func = generate_dig_sequence(3)
|
print(check_for_nonzeros(puzzle))
|
||||||
#print(next(func))
|
|
||||||
[print(a) for a in next(func)]
|
solver.display_grid(puzzle)
|
||||||
|
solver.display(solver.solve(solver.parse_grid(puzzle)))
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
def cross(array1, array2):
|
||||||
|
"""Cross product of elements in A and elements in B."""
|
||||||
|
return [a+b for a in array1 for b in array2]
|
||||||
|
|
||||||
|
|
||||||
|
digits = '123456789'
|
||||||
|
rows = 'ABCDEFGHI'
|
||||||
|
cols = digits
|
||||||
|
squares = cross(rows, cols)
|
||||||
|
unitlist = ([cross(rows, c) for c in cols] +
|
||||||
|
[cross(r, cols) for r in rows] +
|
||||||
|
[cross(rs, cs) for rs in ('ABC', 'DEF', 'GHI') for cs in ('123','456','789')])
|
||||||
|
units = dict((s, [u for u in unitlist if s in u])
|
||||||
|
for s in squares)
|
||||||
|
peers = dict((s, set(sum(units[s], []))-set([s]))
|
||||||
|
for s in squares)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_grid(grid):
|
||||||
|
"""Convert grid to a dict of possible values, {square: digits}, or
|
||||||
|
return False if a contradiction is detected."""
|
||||||
|
# To start, every square can be any digit; then assign values from the grid.
|
||||||
|
values = dict((s, digits) for s in squares)
|
||||||
|
for s, d in grid_values(grid).items():
|
||||||
|
if d in digits and not assign(values, s, d):
|
||||||
|
return False # (Fail if we can't assign d to square s.)
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def grid_values(grid):
|
||||||
|
"""Convert grid into a dict of {square: char} with '0' or '.' for empties."""
|
||||||
|
chars = [c for c in grid if c in digits or c in '0.']
|
||||||
|
assert len(chars) == 81
|
||||||
|
return dict(zip(squares, chars))
|
||||||
|
|
||||||
|
|
||||||
|
def display(values):
|
||||||
|
"""Display these values as a 2-D grid."""
|
||||||
|
width = 1+max(len(values[s]) for s in squares)
|
||||||
|
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 display_grid(grid):
|
||||||
|
"""Display these values as a 2-D grid."""
|
||||||
|
line = '+'.join(['- '*3]*3)
|
||||||
|
for i in range(9):
|
||||||
|
row = ''
|
||||||
|
for j in range(9):
|
||||||
|
row = row + grid[i*9+j] + ' '
|
||||||
|
if j == 2 or j == 5:
|
||||||
|
row = row + '|'
|
||||||
|
print(row)
|
||||||
|
if i == 2 or i == 5:
|
||||||
|
print(line)
|
||||||
|
print('')
|
||||||
|
|
||||||
|
|
||||||
|
def assign(values, s, d):
|
||||||
|
"""Eliminate all the other values (except d) from values[s] and propagate.
|
||||||
|
Return values, except return False if a contradiction is detected."""
|
||||||
|
other_values = values[s].replace(str(d), '')
|
||||||
|
if all(eliminate(values, s, d2) for d2 in other_values):
|
||||||
|
return values
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def eliminate(values, s, d):
|
||||||
|
"""Eliminate d from values[s]; propagate when values or places <= 2.
|
||||||
|
Return values, except return False if a contradiction is detected."""
|
||||||
|
if d not in values[s]:
|
||||||
|
return values # Already eliminated
|
||||||
|
values[s] = values[s].replace(d, '')
|
||||||
|
# (1) If a square s is reduced to one value d2, then eliminate d2 from the peers.
|
||||||
|
if len(values[s]) == 0:
|
||||||
|
return False # Contradiction: removed last value
|
||||||
|
elif len(values[s]) == 1:
|
||||||
|
d2 = values[s]
|
||||||
|
if not all(eliminate(values, s2, d2) for s2 in peers[s]):
|
||||||
|
return False
|
||||||
|
# (2) If a unit u is reduced to only one place for a value d, then put it there.
|
||||||
|
for u in units[s]:
|
||||||
|
dplaces = [s for s in u if d in values[s]]
|
||||||
|
if len(dplaces) == 0:
|
||||||
|
return False # Contradiction: no place for this value
|
||||||
|
elif len(dplaces) == 1:
|
||||||
|
# d can only be in one place in unit; assign it there
|
||||||
|
if not assign(values, dplaces[0], d):
|
||||||
|
return False
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
#def solve(grid): return search(parse_grid(grid))
|
||||||
|
def solve(values): return search(values)
|
||||||
|
|
||||||
|
|
||||||
|
def search(values):
|
||||||
|
"""Using depth-first search and propagation, try all possible values."""
|
||||||
|
if values is False:
|
||||||
|
return False # Failed earlier
|
||||||
|
if all(len(values[s]) == 1 for s in squares):
|
||||||
|
return values # Solved!
|
||||||
|
# Chose the unfilled square s with the fewest possibilities
|
||||||
|
n, s = min((len(values[s]), s) for s in squares if len(values[s]) > 1)
|
||||||
|
return some(search(assign(values.copy(), s, d))
|
||||||
|
for d in values[s])
|
||||||
|
|
||||||
|
|
||||||
|
def some(seq):
|
||||||
|
"""Return some element of seq that is true."""
|
||||||
|
for e in seq:
|
||||||
|
if e:
|
||||||
|
return e
|
||||||
|
return False
|
Loading…
Reference in New Issue