|
|
|
@ -9,7 +9,9 @@ import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cross(items_a, items_b):
|
|
|
|
|
"Cross product of elements in A and elements in B."
|
|
|
|
|
"""
|
|
|
|
|
Cross product of elements in A and elements in B.
|
|
|
|
|
"""
|
|
|
|
|
return [a + b for a in items_a for b in items_b]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -27,7 +29,7 @@ peers = {s: set(sum(units[s], [])) - {s} for s in squares} # noqa: RUF017
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test():
|
|
|
|
|
"A set of unit tests."
|
|
|
|
|
"""A set of unit tests."""
|
|
|
|
|
assert len(squares) == 81
|
|
|
|
|
assert len(unitlist) == 27
|
|
|
|
|
assert all(len(units[s]) == 3 for s in squares)
|
|
|
|
@ -47,8 +49,10 @@ def test():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_grid(grid):
|
|
|
|
|
"""Convert grid to a dict of possible values, {square: digits}, or
|
|
|
|
|
return False if a contradiction is detected."""
|
|
|
|
|
"""
|
|
|
|
|
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 = {s: digits for s in squares}
|
|
|
|
|
for s, d in grid_values(grid).items():
|
|
|
|
@ -58,15 +62,19 @@ def parse_grid(grid):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def grid_values(grid):
|
|
|
|
|
"Convert grid into a dict of {square: char} with '0' or '.' for empties."
|
|
|
|
|
"""
|
|
|
|
|
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 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."""
|
|
|
|
|
"""
|
|
|
|
|
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(d, "")
|
|
|
|
|
if all(eliminate(values, s, d2) for d2 in other_values):
|
|
|
|
|
return values
|
|
|
|
@ -75,8 +83,10 @@ def assign(values, s, d):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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."""
|
|
|
|
|
"""
|
|
|
|
|
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, "")
|
|
|
|
@ -99,7 +109,9 @@ def eliminate(values, s, d):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def display(values):
|
|
|
|
|
"Display these values as a 2-D grid."
|
|
|
|
|
"""
|
|
|
|
|
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:
|
|
|
|
@ -114,11 +126,14 @@ def display(values):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def solve(grid):
|
|
|
|
|
"""
|
|
|
|
|
Solve the grid.
|
|
|
|
|
"""
|
|
|
|
|
return search(parse_grid(grid))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def some(seq):
|
|
|
|
|
"Return some element of seq that is true."
|
|
|
|
|
"""Return some element of seq that is true."""
|
|
|
|
|
for e in seq:
|
|
|
|
|
if e:
|
|
|
|
|
return e
|
|
|
|
@ -126,7 +141,9 @@ def some(seq):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def search(values):
|
|
|
|
|
"Using depth-first search and propagation, try all possible 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):
|
|
|
|
@ -137,9 +154,11 @@ def search(values):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def solve_all(grids, name="", showif=0.0):
|
|
|
|
|
"""Attempt to solve a sequence of grids. Report results.
|
|
|
|
|
"""
|
|
|
|
|
Attempt to solve a sequence of grids. Report results.
|
|
|
|
|
When showif is a number of seconds, display puzzles that take longer.
|
|
|
|
|
When showif is None, don't display any puzzles."""
|
|
|
|
|
When showif is None, don't display any puzzles.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def time_solve(grid):
|
|
|
|
|
start = time.monotonic()
|
|
|
|
@ -162,7 +181,9 @@ def solve_all(grids, name="", showif=0.0):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def solved(values):
|
|
|
|
|
"A puzzle is solved if each unit is a permutation of the digits 1 to 9."
|
|
|
|
|
"""
|
|
|
|
|
A puzzle is solved if each unit is a permutation of the digits 1 to 9.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def unitsolved(unit):
|
|
|
|
|
return {values[s] for s in unit} == set(digits)
|
|
|
|
@ -172,13 +193,15 @@ def solved(values):
|
|
|
|
|
|
|
|
|
|
def from_file(filename, sep="\n"):
|
|
|
|
|
"Parse a file into a list of strings, separated by sep."
|
|
|
|
|
return open(filename).read().strip().split(sep) # noqa: SIM115
|
|
|
|
|
return open(filename).read().strip().split(sep)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def random_puzzle(assignments=17):
|
|
|
|
|
"""Make a random puzzle with N or more assignments. Restart on contradictions.
|
|
|
|
|
"""
|
|
|
|
|
Make a random puzzle with N or more assignments. Restart on contradictions.
|
|
|
|
|
Note the resulting puzzle is not guaranteed to be solvable, but empirically
|
|
|
|
|
about 99.8% of them are solvable. Some have multiple solutions."""
|
|
|
|
|
about 99.8% of them are solvable. Some have multiple solutions.
|
|
|
|
|
"""
|
|
|
|
|
values = {s: digits for s in squares}
|
|
|
|
|
for s in shuffled(squares):
|
|
|
|
|
if not assign(values, s, random.choice(values[s])):
|
|
|
|
@ -190,7 +213,9 @@ def random_puzzle(assignments=17):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def shuffled(seq):
|
|
|
|
|
"Return a randomly shuffled copy of the input sequence."
|
|
|
|
|
"""
|
|
|
|
|
Return a randomly shuffled copy of the input sequence.
|
|
|
|
|
"""
|
|
|
|
|
seq = list(seq)
|
|
|
|
|
random.shuffle(seq)
|
|
|
|
|
return seq
|
|
|
|
|