You could try a stack-based approached. Either using the call stack via recursion, where the backtracking is returning false up the stack:
def solveBoard(partialBoard):
nextUnsolvedBlock = getNextBlock(partialBoard)
possibles = generatePossiblePositions(partialBoard)
for possibility in possibles:
result = solveBoard(partialBoard)
if result.valid:
return result;
This approach is limited largely by the size of the stack; it is not tail-recusive, so the stack must grow, and its maximum size is the number of steps from empty board to complete board.
The alternative is to construct your own stack, which will permit many more such steps because it will be stored on the heap:
def solveBoard(partialBoard):
stack = [(partialBoard,0,0)] // (board, nextBlock, blockOptionIndex)
while stack.last[0].valid == false:
nextBlockOption = getNextBlockOption(stack.last)
if nextBlockOption == None:
pop(stack)
nextBlock = getNextBlock(stack.last)
if nextBlock = None:
exit("No solution")
else:
stack.last[2] = nextBlock
else:
stack.last[1] = nextBlockOption
return stack.last[0]
For bonus points, redo the stack approach using a generator say generateBoards
, which starts with a given board and keeps generating new boards in a consistent pattern. That way your algo would be just:
def solveBoard(initialBoard):
for board in generateBoards(initialBoard):
if isValid(board):
return board
return "No solution found"
And the complexity is then really in generateBoards:
def generateBoards(partialBoard):
nextUnsolvedBlock = getNextBlock(partialBoard)
for possibility in generatePossiblePositions(partialBoard):
yield possibility
If you write generatePossiblePositions
also as a generator, then these two can work together until the thing is done. Because this uses generators rather than recursion, the stack does not grow, and the new boards are generated as you need them rather than all in advance, so the storage requirements are also low. Rather elegant, really, with the power of generators.