"self" 如何正确更新原始变量? Recursion/Backtracking 在 N 皇后问题中 (Python)
How can "self" update original variable correctly? Recursion/Backtracking in the N-queens problem (Python)
这是我的 python 解决 8 皇后问题的程序。除了打印解决电路板的最后一步外,一切正常。我使用 recursion/backtracking 用皇后填充棋盘,直到找到解决方案。包含解决方案的棋盘对象是 self
,它是对 b1
的引用,所以我假设我初始化的原始棋盘 b1
将更新为包含最终求解的棋盘,并会使用 printBoard
打印解决方案。但是,b1
没有更新,并且由于某种未知原因打印时出现故障板。
编辑:在 solve
中添加了 placeQueen
EMPTY = 0
QUEEN = 1
RESTRICTED = 2
class Board:
# initializes a 8x8 array
def __init__ (self):
self.board = [[EMPTY for x in range(8)] for y in range(8)]
# pretty prints board
def printBoard(self):
for row in self.board:
print(row)
# places a queen on a board
def placeQueen(self, x, y):
# restricts row
self.board[y] = [RESTRICTED for i in range(8)]
# restricts column
for row in self.board:
row[x] = RESTRICTED
# places queen
self.board[y][x] = QUEEN
self.fillDiagonal(x, y, 0, 0, -1, -1) # restricts top left diagonal
self.fillDiagonal(x, y, 7, 0, 1, -1) # restructs top right diagonal
self.fillDiagonal(x, y, 0, 7, -1, 1) # restricts bottom left diagonal
self.fillDiagonal(x, y, 7, 7, 1, 1) # restricts bottom right diagonal
# restricts a diagonal in a specified direction
def fillDiagonal(self, x, y, xlim, ylim, xadd, yadd):
if x != xlim and y != ylim:
self.board[y + yadd][x + xadd] = RESTRICTED
self.fillDiagonal(x + xadd, y + yadd, xlim, ylim, xadd, yadd)
# recursively places queens such that no queen shares a row or
# column with another queen, or in other words, no queen sits on a
# restricted square. Should solve by backtracking until solution is found.
def solve(self, col):
if col == -1:
return True
for i in range(8):
if self.board[i][col] == EMPTY:
temp = self.copy()
self.placeQueen(col, i)
if self.solve(col - 1):
return True
temp.board[i][col] = RESTRICTED
self = temp.copy()
return False
# deep copies a board onto another board
def copy(self):
copy = Board()
for i in range(8):
for j in range (8):
copy.board[j][i] = self.board[j][i]
return copy
b1 = Board()
b1.solve(7)
b1.printBoard()
我知道我的实际求解器正在工作,因为当我像这样添加 printBoard
时:
if col == -1:
self.printBoard()
return True
在solve
方法中,打印出解出的板子。简而言之,为什么板的 self
实例没有更新 b1
?
我认为您的问题与在 solve 方法中重新定义 self 有关,我什至不确定您为什么要那样做。
有关详细信息,请参阅此问题:Is it safe to replace a self object by another object of the same type in a method?
像您一样重新分配 self 并不是重新分配 "b1" 引用。因此,当您再次引用 b1 并执行 printBoard 时,您引用的对象与 "self.printBoard()" 将在 solve 完成时引用的对象不同。
我会退后一步,问问自己为什么一开始要替换自己,这对你有什么好处。您可能不需要也不应该这样做。
我不确定这是如何工作的,因为从未调用过 placeQueen
。因此,我不认为按照建议添加印刷品会呈现成品板(我认为它是空的)。 [注意:最新更新修复了这个问题]
使用受限方块的想法是可行的,但是这里的实现方式(没有撤消选项)效率低下;为每个内部循环复制一个全新的 Board
对象非常昂贵。对于所有的麻烦,我们也可以在每次移动时执行迭代冲突检查,这至少可以节省新堆对象的分配和垃圾收集成本。
就 return 完成的棋盘结果而言,在失败时使用 return 值 self
或 self.board
和 None
而不是 True
和 False
.
其他几点:
- 由于解决难题不需要状态,而且我们可以(希望)同意复制棋盘效率低下,我不确定允许
__init__
方法是否有很多意义。 class 作为封装结构很好,我们应该将 EMPTY
、QUEEN
等静态变量隐藏在 Board
class 中,而不管 class 是静态的或实例化的。
- 如果您决定保持 class 有状态,
printBoard
不应生成 side effects——而是覆盖 __str__
。
- 不要在整个代码中 hardcode 大小文字,例如 8;这使得 class 死板、难以维护并且容易出现拼写错误和差一错误。请改用
len(self.board)
并自由提供参数。
fillDiagonal
不需要递归。考虑使用列表理解或 numpy 来简化此矩阵遍历逻辑。
- 使用
snake_case
变量名和 docstrings instead of hashtag comments per PEP-8。如果您觉得有必要写一个像 # restricts column
这样的评论,请考虑将相关块移动到一个名为 restrict_column(...)
的函数并跳过评论。
这是实现其中一些要点的初始重写:
class Board:
EMPTY = 0
QUEEN = 1
DIRS = [(x, y) for x in range(-1, 2) for y in range(-1, 2) if x]
def __init__ (self, size=8):
self.board = [[Board.EMPTY] * size for _ in range(size)]
def __str__(self):
return "\n".join(map(str, self.board))
def legal_from(self, row, col, dr, dc):
while row >= 0 and row < len(self.board) and \
col >= 0 and col < len(self.board[row]):
if self.board[row][col] != Board.EMPTY:
return False
row += dr; col += dc
return True
def legal_move(self, row, col):
return all([self.legal_from(row, col, *d) for d in Board.DIRS])
def solve(self, row=0):
if row >= len(self.board):
return self
for col in range(len(self.board[row])):
if self.legal_move(row, col):
self.board[row][col] = Board.QUEEN
if self.solve(row + 1):
return self
self.board[row][col] = Board.EMPTY
if __name__ == "__main__":
for result in [Board(i).solve() for i in range(9)]:
print(result, "\n")
输出:
[1]
None
None
[0, 1, 0, 0]
[0, 0, 0, 1]
[1, 0, 0, 0]
[0, 0, 1, 0]
[1, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 1]
[0, 1, 0, 0, 0]
[0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 0, 0, 1, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]
这是我的 python 解决 8 皇后问题的程序。除了打印解决电路板的最后一步外,一切正常。我使用 recursion/backtracking 用皇后填充棋盘,直到找到解决方案。包含解决方案的棋盘对象是 self
,它是对 b1
的引用,所以我假设我初始化的原始棋盘 b1
将更新为包含最终求解的棋盘,并会使用 printBoard
打印解决方案。但是,b1
没有更新,并且由于某种未知原因打印时出现故障板。
编辑:在 solve
placeQueen
EMPTY = 0
QUEEN = 1
RESTRICTED = 2
class Board:
# initializes a 8x8 array
def __init__ (self):
self.board = [[EMPTY for x in range(8)] for y in range(8)]
# pretty prints board
def printBoard(self):
for row in self.board:
print(row)
# places a queen on a board
def placeQueen(self, x, y):
# restricts row
self.board[y] = [RESTRICTED for i in range(8)]
# restricts column
for row in self.board:
row[x] = RESTRICTED
# places queen
self.board[y][x] = QUEEN
self.fillDiagonal(x, y, 0, 0, -1, -1) # restricts top left diagonal
self.fillDiagonal(x, y, 7, 0, 1, -1) # restructs top right diagonal
self.fillDiagonal(x, y, 0, 7, -1, 1) # restricts bottom left diagonal
self.fillDiagonal(x, y, 7, 7, 1, 1) # restricts bottom right diagonal
# restricts a diagonal in a specified direction
def fillDiagonal(self, x, y, xlim, ylim, xadd, yadd):
if x != xlim and y != ylim:
self.board[y + yadd][x + xadd] = RESTRICTED
self.fillDiagonal(x + xadd, y + yadd, xlim, ylim, xadd, yadd)
# recursively places queens such that no queen shares a row or
# column with another queen, or in other words, no queen sits on a
# restricted square. Should solve by backtracking until solution is found.
def solve(self, col):
if col == -1:
return True
for i in range(8):
if self.board[i][col] == EMPTY:
temp = self.copy()
self.placeQueen(col, i)
if self.solve(col - 1):
return True
temp.board[i][col] = RESTRICTED
self = temp.copy()
return False
# deep copies a board onto another board
def copy(self):
copy = Board()
for i in range(8):
for j in range (8):
copy.board[j][i] = self.board[j][i]
return copy
b1 = Board()
b1.solve(7)
b1.printBoard()
我知道我的实际求解器正在工作,因为当我像这样添加 printBoard
时:
if col == -1:
self.printBoard()
return True
在solve
方法中,打印出解出的板子。简而言之,为什么板的 self
实例没有更新 b1
?
我认为您的问题与在 solve 方法中重新定义 self 有关,我什至不确定您为什么要那样做。
有关详细信息,请参阅此问题:Is it safe to replace a self object by another object of the same type in a method?
像您一样重新分配 self 并不是重新分配 "b1" 引用。因此,当您再次引用 b1 并执行 printBoard 时,您引用的对象与 "self.printBoard()" 将在 solve 完成时引用的对象不同。
我会退后一步,问问自己为什么一开始要替换自己,这对你有什么好处。您可能不需要也不应该这样做。
我不确定这是如何工作的,因为从未调用过 placeQueen
。因此,我不认为按照建议添加印刷品会呈现成品板(我认为它是空的)。 [注意:最新更新修复了这个问题]
使用受限方块的想法是可行的,但是这里的实现方式(没有撤消选项)效率低下;为每个内部循环复制一个全新的 Board
对象非常昂贵。对于所有的麻烦,我们也可以在每次移动时执行迭代冲突检查,这至少可以节省新堆对象的分配和垃圾收集成本。
就 return 完成的棋盘结果而言,在失败时使用 return 值 self
或 self.board
和 None
而不是 True
和 False
.
其他几点:
- 由于解决难题不需要状态,而且我们可以(希望)同意复制棋盘效率低下,我不确定允许
__init__
方法是否有很多意义。 class 作为封装结构很好,我们应该将EMPTY
、QUEEN
等静态变量隐藏在Board
class 中,而不管 class 是静态的或实例化的。 - 如果您决定保持 class 有状态,
printBoard
不应生成 side effects——而是覆盖__str__
。 - 不要在整个代码中 hardcode 大小文字,例如 8;这使得 class 死板、难以维护并且容易出现拼写错误和差一错误。请改用
len(self.board)
并自由提供参数。 fillDiagonal
不需要递归。考虑使用列表理解或 numpy 来简化此矩阵遍历逻辑。- 使用
snake_case
变量名和 docstrings instead of hashtag comments per PEP-8。如果您觉得有必要写一个像# restricts column
这样的评论,请考虑将相关块移动到一个名为restrict_column(...)
的函数并跳过评论。
这是实现其中一些要点的初始重写:
class Board:
EMPTY = 0
QUEEN = 1
DIRS = [(x, y) for x in range(-1, 2) for y in range(-1, 2) if x]
def __init__ (self, size=8):
self.board = [[Board.EMPTY] * size for _ in range(size)]
def __str__(self):
return "\n".join(map(str, self.board))
def legal_from(self, row, col, dr, dc):
while row >= 0 and row < len(self.board) and \
col >= 0 and col < len(self.board[row]):
if self.board[row][col] != Board.EMPTY:
return False
row += dr; col += dc
return True
def legal_move(self, row, col):
return all([self.legal_from(row, col, *d) for d in Board.DIRS])
def solve(self, row=0):
if row >= len(self.board):
return self
for col in range(len(self.board[row])):
if self.legal_move(row, col):
self.board[row][col] = Board.QUEEN
if self.solve(row + 1):
return self
self.board[row][col] = Board.EMPTY
if __name__ == "__main__":
for result in [Board(i).solve() for i in range(9)]:
print(result, "\n")
输出:
[1]
None
None
[0, 1, 0, 0]
[0, 0, 0, 1]
[1, 0, 0, 0]
[0, 0, 1, 0]
[1, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 1]
[0, 1, 0, 0, 0]
[0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 0, 0, 1, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]