递归函数[Python]中变量作用域的问题
我对Python比较陌生,我一直在研究checkio.com上的问题,其中一些问题我觉得非常有趣 目前我正在研究的问题是一个数独解算器(如果有人需要我写出数独规则,我很乐意帮忙) 我决定尝试使用回溯和递归函数来解决这个问题。该函数采用2d数组形式的初始数独网格输入,零表示空单元格 下面是我代码的核心部分:递归函数[Python]中变量作用域的问题,python,Python,我对Python比较陌生,我一直在研究checkio.com上的问题,其中一些问题我觉得非常有趣 目前我正在研究的问题是一个数独解算器(如果有人需要我写出数独规则,我很乐意帮忙) 我决定尝试使用回溯和递归函数来解决这个问题。该函数采用2d数组形式的初始数独网格输入,零表示空单元格 下面是我代码的核心部分: def checkio(grid,depth=0): # Return the solution of the sudoku # or False if the current
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
以下是我正在使用的子程序,它们似乎工作正常:
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
下面是一个已知(唯一)可解的输入网格函数调用示例:
checkio([[0,7,1,6,8,4,0,0,0,0],[0,4,9,7,0,0,0,0,0],[5,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,4],[0,0,0,0,3,0,0,0,0,0,0,0,0,0],[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
现在,我希望这段代码能够工作。但是如果您运行测试示例,它将失败。通过查看中间网格,我们可以看到一个问题:一旦算法尝试了给定的轨迹并卡住(即到达一个没有候选的空正方形),函数的后续实例(在较低深度操作)正在使用的网格将填充(可能不正确)以前的函数实例留下的值(在更高的深度上操作)
我不明白为什么会这样。我认为在每次函数调用时,都会创建一个新的局部作用域,这样函数的每个实例都可以使用自己的网格,而不会影响其他实例的网格
我是否误解了变量范围,或者我是否犯了其他错误?谢谢您的时间。您正在传递一个可变对象。本地名称不共享,但它们引用同一个对象,直到您为它们指定了不同的对象
当引用一个可变对象时,对象本身的更改将通过对它的所有引用(例如,调用堆栈中的所有本地名称)可见
在更改网格之前创建副本:
grid = [row[:] for row in grid]
这会将一个新的列表对象绑定到本地名称网格
,而堆栈中的其他本地对象不绑定到其中一个。由于外部列表中包含更多的可变列表,因此也需要复制这些列表(通过使用整个切片语法)。您正确地认为网格变量是每个递归调用范围的局部变量,但变量只是对对象的引用
问题是每个范围中的网格
变量都引用相同的对象,即在开始时传入的列表
当您在一个范围内修改由grid
引用的对象时,您正在所有范围内修改它,因为每个范围内的grid
变量都引用同一个对象
您要做的是修改传入的网格副本,保持原始网格不变
您可以复制一个嵌套很深的数据结构,就像您在函数中所做的那样:
from copy import deepcopy
def checkio(grid,depth=0):
grid = deepcopy(grid)
...
这样,当您找到一个无效的解决方案,并在回溯中的每一步开始回溯时,网格将保持在您进入特定“死胡同”之前的状态。添加一行代码解决了问题!谢谢你如此简洁地描述了我的知识差距——我会确保阅读有关可变对象的内容。