python - 在 Python 中查找数独隐藏单曲的有效方法
问题描述
我正在编写一个具有类人策略的数独求解器。我写了一些方法来在一个盒子上找到隐藏的单曲,也就是说,检查是否只有一个单元格可供给定的候选人放置在盒子里(“隐藏”意味着即使单元格本身有更多的候选人) .
我对列表使用以下结构:board[9][9]
存储从 1 到 9 的放置数字,如果没有放置数字,则存储 0。存储给定单元格的possibles[9][9][9]
候选者,如果候选者已经被淘汰,则为 0。由于我也在使用 Pygame 编写 GUI,possibles
因此我不喜欢从possibles[i][j] = [0, 0, 0, 0, 5, 0, 0, 0, 0]
.
这是隐藏的单一方法:
def hidden_singles(board, possibles):
# CHECK BOX
print("method 1")
hiddenSinglesPos_box = []
t0 = time.time()
iterations = 0
# iterate over each cell in the board
for i, row in enumerate(board):
for j, bnum in enumerate(row):
# if the cell already has a placed number, skip
if bnum != 0:
continue
# get box index
ii = 3 * (i // 3)
jj = 3 * (j // 3)
# reduce the candidate list of cell to only non-zeros
p = [_ for _ in possibles[i][j] if _ != 0]
# for each candidate, check the box for a hidden single !?!?
for k, pnum in enumerate(p):
count = 0
for ibox in range(ii, ii + 3):
for jbox in range(jj, jj + 3):
# skip again if the cell within the box has a placed number
if board[ibox][jbox] != 0:
continue
iterations += 1
if possibles[ibox][jbox][pnum - 1] != 0:
count += 1
if count == 1:
hiddenSinglesPos_box.append([i, j, pnum])
deltaT = time.time() - t0
print(deltaT)
print(f"Iterations: {iterations}")
print(f"{len(hiddenSinglesPos_box)} hidden singles")
print(hiddenSinglesPos_box)
值得一提的是,在调用这个方法之前,我已经通过勾选row、col和box,剔除了明显的非候选。
这很有效,它找到了大约 1000 次迭代的隐藏单曲,但它肯定可以改进。我注意到,一旦找到第二个匹配项,我就可以从框搜索中删除候选人。例如,如果框中的第一个单元格有候选 [1, 2, 3] 而第二个单元格有 [1, 2, 4],则不需要检查第二个单元格的候选 1 和 2(我会不知道如何做到这一点而不会使一切过于复杂)。我确实访问了每个板单元,因为这是我发现的方式,不仅可以跟踪盒子中隐藏单的存在,还可以跟踪它的坐标。
我是 Python 和编码方面的初学者,所以我接受有关此方法或一般结构以及我如何存储板、可能性等的建议。
解决方案
你有一个有趣的问题。要获得“更少”的迭代,请使用集合之类的抽象。这使得解释器调用编译的例程(主要用 c 编写)而不是解析复杂的 python 逻辑。
board = [
[0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 3, 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, 7, 0, 0, 0, 0, 0, 0],
[0, 0, 8, 6, 5, 4, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
]
fullSet = list(range(1, len(board)+1))
zero = set([0])
def findPossibles(board):
result = [None] * len(board)
for y in range(len(board)):
result[y] = [None] * len(board[0])
for x in range(len(board[0])):
result[y][x] = fullSet.copy()
for y, row in enumerate(result):
for x, e in enumerate(row):
if board[y][x] != 0:
e = [0] * 9
for i in range(len(board)):
num = board[i][x]
if num != 0: e[num-1] = 0
for i in range(len(board[0])):
num = board[y][i]
if num != 0: e[num-1] = 0
return result
def findSingles(board, possibles):
result = []
for y in range(3):
for x in range(3):
taken = set()
# collect quadrant values
for iy in range(y*3, y*3+3):
for ix in range(x*3, x*3+3):
taken.add(board[iy][ix])
# find hidden singles
for iy in range(y*3, y*3+3):
for ix in range(x*3, x*3+3):
res = set(possibles[iy][ix]) - taken - zero
if len(res) == 1:
result.append((ix, iy, list(res)[0]))
return result
import time
t0 = time.time()
possibles = findPossibles(board)
singles = findSingles(board, possibles)
print(time.time() - t0)
print(singles) # [(1, 7, 9)]
处理隐藏单曲的代码应该进行大约 162 次迭代。
编辑
我想出了这个算法,使用字典来收集象限内一个数字的所有可能位置。然后收集只有一个可能位置的那些。我还改进了列表的外部复杂性findPossibles
并用集合替换了列表。我们可以在评论中进一步讨论代码。
board = [
[9, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 3, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 9, 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, 7, 9, 0, 0, 0, 0, 0],
[0, 0, 8, 6, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 9, 0, 0]
]
fullSet = set(range(1, len(board)+1))
def findPossibles(board):
sz = len(board)
br = range(sz)
result = [None] * sz
for y in br:
result[y] = [None] * sz
for x in br:
result[y][x] = fullSet.copy()
for y, row in enumerate(board):
taken = set(row)
for x in br:
result[y][x] -= taken
for x in br:
taken = set()
for y in br:
taken.add(board[y][x])
for y in br:
result[y][x] -= taken
for y in range(3):
for x in range(3):
taken = set()
rx = x * 3
ry = y * 3
for iy in range(ry, ry+3):
for ix in range(rx, rx+3):
taken.add(board[iy][ix])
for iy in range(ry, ry+3):
for ix in range(rx, rx+3):
result[iy][ix] -= taken
return result
def findSingles(possibles):
result = []
for y in range(3):
for x in range(3):
table = {i:[] for i in fullSet}
rx = x * 3
ry = y * 3
for iy in range(ry, ry+3):
for ix in range(rx, rx+3):
for p in possibles[iy][ix]:
table[p].append((ix, iy))
for p in table:
if len(table[p]) == 1:
result.append(table[p][0] + (p,))
return result
import time
t0 = time.time()
possibles = findPossibles(board)
singles = findSingles(possibles)
print(time.time() - t0)
print(singles) # [(1, 7, 9)]
编辑 2
为了改进该方法findPossibles
,我们应该为已放置数字的单元格中的候选者分配一个空集。
def findPossibles(board):
sz = len(board)
br = range(sz)
result = [None] * sz
for y in br:
result[y] = [None] * sz
for x in be:
# check if this board cell is empty
if board[y][x] == 0:
result[y][x] = fullSet.copy()
else:
result[y][x] = set()
推荐阅读
- c# - 索引属性的 INotifyPropertyChanged
- python - 如何在 Python 中为多行正则表达式 sub (re.sub)?
- c# - 如何从局部变量中保持位置?
- node.js - NodeJS:用 JavaScript 内容编写 http 响应
- assembly - 对数组进行排序的 MIPS 程序集
- confidence-interval - mgcv::gam 预测的协方差
- r - 'mice' R 包没有输入数据
- css - Bulma - 在一个浏览器窗口上调整图像和页脚而不滚动
- javascript - 邮递员中的响应数据与终端或 chrome 中的不同
- angular - Angular 6双重订阅问题