Simply Solving Sudoku

Posted by Graham Wheeler on Thursday, September 10, 2015

It’s been a long time since I last blogged. I’ve been meaning to for oh so long but you know what they say about the road to hell. For a while I maintained my math blog but even that has been fallow for some time.

Fortunately, I stumbled upon Peter Norvig’s article about solving Sudoku, and that has provided the impetus. His approach is probably the most sensible I have seen for a while; there seem to be some really bad solvers out there. I was unimpressed with the one by Skiena in the Algorithm Design Manual, although the truly ridiculous one has to be the so-overblown-I-took-a-whole-book approach in Programming Sudoku; the mind simply boggles at how complex some people make trivial things.

Because solving Sudoku is indeed trivial. There is nothing to it. All you need is a very simple backtracking search. I wrote the solver below originally more than 10 years ago in Javascript and it was used as a generator running on extremely low-powered Microsoft SPOT smart watches. For fun I dug it out, turned it into Python, and tested it on “the world’s hardest Sudoku puzzle”. How fast can you blink?

But still some people make a big deal out of it all

#!python
rows = [0xffff] * 9
cols = [0xffff] * 9
rgns = [0xffff] * 9
board = [0]*81

def init(puz):
    global board, rows, cols, rgns, board
    for r, row in enumerate(puz):
        for c, ch in enumerate(row):
            board[r * 9 + c] = ch
            if ch == '.':
                continue
            m = ~(1 << (ord(ch) - 48))
            rows[r] &= m
            cols[c] &= m
            rgns[(r // 3) * 3 + (c // 3)] &= m

def fill(pos):
    global board, rows, cols, rgns
    if pos == 81:
        return True
    elif board[pos] != '.':
        return fill(pos + 1)
    r = pos // 9
    c = pos % 9
    allowed = rows[r] & cols[c]
    if allowed:
        rg = (r // 3) * 3 + (c // 3)
        allowed &= rgns[rg]
        if allowed:
            for i in range(1, 10):
                tm = 1 << i
                if allowed & tm:
                    im = ~tm
                    rows[r] &= im
                    cols[c] &= im
                    rgns[rg] &= im
                    if fill(pos + 1):
                        board[pos] = chr(48 + i)
                        return True
                    rows[r] |= tm
                    cols[c] |= tm
                    rgns[rg] |= tm
    return False

def solve(puz):
    init(puz)
    return fill(0)

if solve([
"1....7.9.",
".3..2...8",
"..96..5..",
"..53..9..",
".1..8...2",
"6....4...",
"3......1.",
".4......7",
"..7...3.."
]):
    for i in xrange(0, 81, 9):
        print ''.join(board[i:i+9])