Skip to content

Instantly share code, notes, and snippets.

@shayelkin
Last active May 21, 2019 18:18
Show Gist options
  • Save shayelkin/9b25ab9e5627a2757a9b4a3ccb172262 to your computer and use it in GitHub Desktop.
Save shayelkin/9b25ab9e5627a2757a9b4a3ccb172262 to your computer and use it in GitHub Desktop.
Soduko solver. Everybody writes one.
#!/usr/bin/env python2
"""
Soduko solver. Everybody writes one.
"""
sample = "003020600900305001001806400008102900700000008006708200002609500800203009005010300"
all_bits = int('1'*9,2)
def load_puzzle(s):
return [all_bits if c in '0.' else 1 << (int(c)-1) for c in s]
sample_puzzle = load_puzzle(sample)
assert 81 == len(sample_puzzle)
def itr_digits(b):
return (d for d in xrange(9) if b & (1 << d))
def itr_bits(b):
return (1 << d for d in xrange(9) if b & (1 << d))
def str_cell(b):
return "".join([str(d+1) for d in itr_digits(b)])
def popcount(i):
count = 0
while i:
i &= (i - 1) # clear the least signficiant bit set
count += 1
return count
assert 0 == popcount(0)
assert 1 == popcount(1)
assert 2 == popcount(3)
def pprint_puzzle(bp):
space_per_cell = max(popcount(c) for c in bp)
for row in xrange(9):
print " ".join("".join(str_cell(bp[row*9+col])).center(space_per_cell) for col in xrange(9))
def neighbour_indices(i):
row, col = i / 9, i % 9
return set([(row*9 + c) for c in xrange(9)] +\
[(r*9 + col) for r in xrange(9)] +\
[(r+3*(row/3))*9 + (c+3*(col/3)) for r in xrange(3) for c in xrange(3)]) - set([row*9+col])
neighbours = [neighbour_indices(i) for i in xrange(81)]
assert all((i not in neighbours[i]) for i in xrange(81))
def validate_cell(bp, i):
"Validate that the value(s) in cell i don't invalidate the Soduko property"
if 0 == popcount(bp[i]):
return False
if 1 == popcount(bp[i]):
for j in neighbours[i]:
if 1 == popcount(bp[j]) and bp[i] & bp[j]:
return False
return True
def validate_puzzle(bp):
"Validate that the whole matrix does not invalidate the Soduko property"
# Not as optimal as it could be, but only used for asserting the result
return all(validate_cell(bp, i) for i in xrange(81))
assert validate_puzzle(sample_puzzle)
def is_solved(bp):
return bp and all((1 == popcount(x)) for x in bp) and validate_puzzle(bp)
assert (not is_solved(sample_puzzle))
def set_value_and_prune(bp, i, bit_v):
if 1 != popcount(bit_v):
raise ValueError("Only set a single value at a time")
bp[i] = bit_v
mask = (~bp[i] & all_bits)
for j in neighbours[i]:
bp[j] = bp[j] & mask
if 0 == popcount(bp[j]):
return False
return bp
def solve(bp):
try:
c_i, c_val, _ = min(((i, x, popcount(x)) for (i, x) in enumerate(bp) if 1 < popcount(x)), key=lambda t: t[2])
except ValueError:
return bp if validate_puzzle(bp) else False
for bit in itr_bits(c_val):
maybe_solution = set_value_and_prune(bp[:], c_i, bit)
if maybe_solution:
maybe_solution = solve(maybe_solution)
if maybe_solution:
return maybe_solution
return False
assert is_solved(solve(sample_puzzle))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment