Brute force sudoku cracker

poissonnier at gmail.com poissonnier at gmail.com
Sun Sep 18 08:19:22 EDT 2005


Had the same reaction as everyone when I saw theses puzzles a month or
so ago, so here is my solution...
the solve function is recursive, so it can also solve the 'deadlock
set' (example3). find_cell looks for an empty cell with the most filled
cells in it's row and column, so the search tree doesn't grow too
'wide'.

-----------------------------------

example1 = """8 9 - - - - 3 - 4
- - 5 - 3 - - - -
- 7 - - 8 1 5 - -
- 4 - - - 7 - - 3
- - - 5 4 3 - - -
2 - - 1 - - - 5 -
- - 7 9 1 - - 4 -
- - - - 7 - 2 - -
9 - 8 - - - - 7 5"""

example2 = """- 5 2 - - - - - -
9 - - 1 - - - 5 -
- - 4 8 3 - - - 2
- 3 - - 9 - 1 - 5
- - - - - - - - -
5 - 7 - 6 - - 4 -
1 - - - 7 3 6 - -
- 7 - - - 9 - - 3
- - - - - - 2 7 -"""

example3 = """- 3 - 5 - - 8 1 -
1 - - 7 6 - - 9 -
4 - - - - - - - -
8 4 3 9 7 5 1 2 6
- 1 - 6 - - - 7 8
6 - - 8 - 1 9 3 -
- - - 1 5 7 - - 9
- 9 - - 8 6 - - 1
- 6 1 - 9 2 - 8 -"""

class ImpossibleException(Exception): pass


def field_from_string(field_str):
    def mapper(x):
        if x == '-': return None
        else: return int(x)
    return [map(mapper, line.split()) for line in
field_str.split('\n')]


def field_from_file(filename):
    f = open(filename)
    field = field_from_string(f.read())
    f.close()
    return field


def print_field(field):
    def mapper(x):
        if x == None: return '  '
        else: return str(x)+' '
    str_rows = [map(mapper, x) for x in field]
    str_rows = ['| ' + " ".join(str_row) + '|' for str_row in str_rows]
    print 'x'+'-'*27+'x'
    print "\n".join(x for x in str_rows)
    print 'x'+'-'*27+'x'


def check_constraint(field, (x,y), num):
    """Checks if putting num at (x,y) is valid."""
    #row constraint
    occ = [elem for elem in field[x] if elem == num]
    if occ:
        return False
    #column constraint
    occ = [field[k][y] for k in range(9) if field[k][y] == num]
    if occ:
        return False
    #subfield constraint
    sub_x, sub_y = x//3, y//3
    occ = [field[k+3*sub_x][l+3*sub_y] for k in range(3) for l in
range(3)
        if field[k+3*sub_x][l+3*sub_y] == num]
    if occ:
        return False
    return True


def find_cell(field):
    """Returns coords of an empty cell or None if all cells are filled.
    Returns cells with most row and column 'neighbours' first."""
    def count(row):
        return len([x for x in row if x is not None])

    #[(count, index), ... ]
    x_counts = zip((count(row) for row in field), range(9))
    sorted_x_counts = sorted(x_counts, reverse=True)
    x_keys = [l for k,l in sorted_x_counts]

    columns = [[field[k][y] for k in range(9)] for y in range(9)]
    y_counts = zip((count(column) for column in columns), range(9))
    sorted_y_counts = sorted(y_counts, reverse=True)
    y_keys = [l for k,l in sorted_y_counts]

    for x in x_keys:
        for y in y_keys:
            if field[x][y] == None:
                return (x,y)
    else:
        return None


def set_value(field, (x,y), num):
    """Returns copy of field with cell (x,y) set to num."""
    #new_field = copy.deepcopy(field)
    new_field = [row[:] for row in field]
    new_field[x][y] = num
    return new_field


def solve(field):
    xy = find_cell(field)
    if not xy:
        return field
    poss = [e for e in range(1,10) if check_constraint(field, xy, e)]
    for e in poss:
        new_field = set_value(field, xy, e)
        try:
            return solve(new_field)
        except ImpossibleException:
            pass #try next possibility
    raise ImpossibleException()


if __name__ == '__main__':
    field = field_from_string(example3)
    print 'initial field:'
    print_field(field)
    print 'solution:'
    try:
        print_field(solve(field))
    except ImpossibleException:
        print 'not solvable'




More information about the Python-list mailing list