Last active
May 21, 2019 18:18
-
-
Save shayelkin/9b25ab9e5627a2757a9b4a3ccb172262 to your computer and use it in GitHub Desktop.
Soduko solver. Everybody writes one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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