I'm relatively new to Python, and I've been working through the problems on checkio.com, some of which I'm finding very interesting.
The current problem I'm working on is a sudoku solver (if anyone needs me to write out the rules of sudoku, I'd be glad to oblige).
I've decided to try solving the problem using backtracking, and a recursive function. The function takes an initial sudoku grid input in the form of a 2d array, with zero denoting an empty cell.
Here the core part of my code:
def checkio(grid,depth=0):
# Return the solution of the sudoku
# or False if the current grid has no solution
# uses backtracking
# depth is for debugging
for p in range(9):
for q in range(9):
if grid[p][q]==0:
candidates=cand(p,q,grid)
for x in candidates:
#try each candidate in turn
grid[p][q]=x
#print out information for debugging
print("working on cell (%s,%s) --> depth %s, candidate is %s" % (p,q,depth,x))
print_sudoku(grid)
h=checkio(grid,depth+1)
#h is False unless we have found a full grid
if h:
return h
#return false if no candidate works
return False
#if the grid is already full, just return it
#if the initial input was valid, this algorithm shouldn't produce an invalid grid
return grid
Here are the subroutines I'm using, which appear to be working properly:
def print_sudoku(grid):
# prints out the grid in a way that's easier to parse visually
for x in grid:
print(x)
def cand(i,j,grid):
# returns a list of candidate numbers for the square (i,j)
rowdata=[]
for n in range(9):
if grid[i][n]!=0:
rowdata.append(grid[i][n])
coldata=[]
for n in range(9):
if grid[n][j]!=0:
coldata.append(grid[n][j])
boxdata=[]
for p in range(9):
for q in range(9):
if samebox(p,q,i,j) and grid[p][q]!=0:
boxdata.append(grid[p][q])
fulllist=range(1,10)
cand=list(set(fulllist) - set(rowdata + coldata + boxdata))
return cand
def samebox(ax,ay,bx,by):
#returns true if (ax,ay) and (bx,by) are in the same box
return ax // 3 == bx // 3 and ay // 3 == by // 3
Here is an example function call with an input grid that is known to be (uniquely) solvable:
checkio([[0,7,1,6,8,4,0,0,0],[0,4,9,7,0,0,0,0,0],[5,0,0,0,0,0,0,0,0],[0,8,0,0,0,0,5,0,4],[0,0,0,3,0,7,0,0,0],[2,0,3,0,0,0,0,9,0],[0,0,0,0,0,0,0,0,9],[0,0,0,0,0,3,7,2,0],[0,0,0,4,9,8,6,1,0]])
Now, I expected this code to work. But if you run the test example, it fails. By looking at the intermediate grids, we can see a problem: once the algorithm has tried a given track and got stuck (i.e. arrived at an empty square with no candidates), the grid that subsequent instances of the function (operating at a lower depth) is working with is filled with the (potentially incorrect) values left by previous instances of the function (operating at a higher depth).
I don't understand why this is happening. I thought that on each function call, a new local scope is created, so that each instance of the function works with its own grid without affecting the grids of the other instances.
Have I misunderstood something about variable scope, or am I making some other mistake? Thank you for your time.