【问题标题】:Trouble with variable scope in recursive function [Python]递归函数中变量范围的问题[Python]
【发布时间】:2014-07-04 10:19:11
【问题描述】:

我对 Python 比较陌生,我一直在解决 checkio.com 上的问题,其中一些我觉得很有趣。

我目前正在解决的问题是数独求解器(如果有人需要我写出数独规则,我很乐意帮忙)。

我决定尝试使用回溯和递归函数来解决问题。该函数采用二维数组形式的初始数独网格输入,零表示空单元格。

这里是我的代码的核心部分:

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,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]])

现在,我希望这段代码可以工作。但是,如果您运行测试示例,它会失败。通过查看中间网格,我们可以看到一个问题:一旦算法尝试了给定的轨道并卡住(即到达没有候选者的空方格),该函数的后续实例的网格(在较低深度下运行) ) 正在使用的函数填充了之前的函数实例留下的(可能不正确的)值(在更高的深度运行)。

我不明白为什么会这样。我认为在每次函数调用时,都会创建一个新的本地范围,以便函数的每个实例都使用自己的网格,而不会影响其他实例的网格。

我是否误解了关于变量范围的某些内容,还是我犯了其他错误?感谢您的宝贵时间。

【问题讨论】:

    标签: python


    【解决方案1】:

    您认为grid 变量在每个递归调用的范围内是正确的,但变量只是对对象的引用。

    问题是每个作用域中的grid 变量都引用同一个对象,即您在开始时传入的list

    当您在一个范围内修改grid 引用的对象时,您是在所有 范围内修改它,因为每个范围内的grid 变量都引用同一个对象。

    您要做的是修改传入的网格副本,保持原始网格不变。

    您可以像使用 deepcopy() 函数一样复制深度嵌套的数据结构:

    from copy import deepcopy
    
    def checkio(grid,depth=0):
        grid = deepcopy(grid)
        ...
    

    这样,当您找到一个无效的解决方案,并在回溯中的每一步开始回溯时,网格将保留在您走下那个特定的“死胡同”之前的状态。

    【讨论】:

      【解决方案2】:

      您正在传递一个可变对象。本地名称不共享,但它们引用同一个对象,直到您为它们分配一个不同的对象。

      在引用该可变对象时,对对象本身的更改随后通过对它的所有引用可见(例如,调用堆栈中的所有本地名称)。

      在更改网格之前创建一个副本:

      grid = [row[:] for row in grid]
      

      这将一个新的列表对象绑定到本地名称grid,堆栈中的其他本地对象未绑定到其中一个。因为外部列表内部包含更多可变列表,所以也需要复制这些列表(通过使用整体切片语法)。

      【讨论】:

      • 添加那一行代码解决了问题!感谢您如此简洁地描述我的知识差距 - 我将确保阅读有关可变对象的内容。
      猜你喜欢
      • 2017-04-08
      • 1970-01-01
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      • 2016-12-18
      • 2011-11-21
      • 1970-01-01
      • 2021-07-15
      相关资源
      最近更新 更多