This code ended up being significantly simpler than I had expected. This solution looks at each empty cell, finds all the numbers that cell could be (aren’t in the same column, row or square) and makes a guess. It then moves on to the next cell and makes another guess. It keeps doing this until it comes to a square with no possibilities, in which case it made a mistake somewhere, or it solves the puzzle. If it made a mistake it backs up to it’s last guess and tries the next possibility. If there’s no possible numbers left, it backs up the guess before that and so on.
The code is an excellent example of the power of recursion.
puzzle = [ 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,3, 0,8,5, 0,0,1, 0,2,0, 0,0,0, 0,0,0, 5,0,7, 0,0,0, 0,0,4, 0,0,0, 1,0,0, 0,9,0, 0,0,0, 0,0,0, 5,0,0, 0,0,0, 0,7,3, 0,0,2, 0,1,0, 0,0,0, 0,0,0, 0,4,0, 0,0,9 ] def listPossible(x,y,puzzle): """ Given a cell coordinate and a puzzle, return all values that do not occur in the square, row or column as that cell """ indexesOfCol = range(y*9,(y+1)*9) indexesOfRow = range(0+x,81+x,9) upperLeftOfSquare = (y - y % 3)*9 + x - x % 3 indexesOfSquare = [upperLeftOfSquare + sqX + sqY for sqX in range(0, 3) for sqY in range(0, 9*3,9)] notPossible = [ puzzle[index] for index in indexesOfRow + indexesOfCol + indexesOfSquare if puzzle[index] != 0 ] return [i for i in range(1,10) if i not in notPossible] def display(puzzle): for y in xrange(9): print " ".join( str(puzzle[x + y*9]) for x in xrange(9) ) print #add a line after the puzzle def treeMethod(puzzle): """ The treeMethod braches out filling in each possibility for a square until it runs into contridiction, then it backs up and tries another brach. """ if 0 not in puzzle: return puzzle #emptyCells will hold information about each empty cell #it will gather the number of possibilities, the index and a list of the possibilities emptyCells = [] for i in xrange(81): if puzzle[i] == 0: possibilities = listPossible( i%9, i/9, puzzle) emptyCells.append( (len(possibilities), i, possibilities) ) # Now emptyCells gets sorted on the number of possibilities # therefore, it will grab the index with the fewest possibilities l, i, possibilities = sorted(emptyCells)[0] for guess in possibilities: puzzle[i] = guess guessResult = treeMethod(puzzle) if guessResult: return guessResult # We've exhaused possibilites, this branch does not contain a valid solution # Set the index i back to 0 and the function will exit (returning None) puzzle[i] = 0 solution = treeMethod(puzzle) display(solution)
A few points of interest. Initially I had a puzzleSolved function that checked if the puzzle was solved correctly. But this method will not solve a puzzle incorrectly if a solution is possible, so I only need to check that if there are any blanks left and if there are none, the puzzle is solved. One thing that surprised me about this solution was how efficient it is. The treeMethod function generally gets called around 200 times to solve a puzzle. This is very good considering that a puzzle generally has about 50 empty cells and that this the minimal number of calls any solver could make. My friend gave me the puzzle that is in there now which is designed to specifically break this sort of brute force solution. My original solution would have taken over 600 million loops to solve. To mitigate this, I find all empty squares and always try the cell with the fewest possibilities which brings it down to around 45 thousand loops.