如何停止在我的数独网格上获得重复项?

How to stop getting duplicates on my sudoku grid?

提问人:Armenta 提问时间:9/7/2023 最后编辑:ShadowRangerArmenta 更新时间:9/7/2023 访问量:69

问:

我已经对所有内容进行了编码,以至于我的程序试图自行解决数独问题,但是当我运行它时,我在网格上得到了重复项。我知道问题出在代码检查每个 3x3 网格的部分,但我找不到问题所在。

这是我的全部代码:

grid2 = [[5,3,0,0,7,0,0,0,0], [6,0,0,1,9,5,0,0,0], [0,9,8,0,0,0,0,6,0], [8,0,0,0,6,0,0,0,3], [4,0,0,8,0,3,0,0,1], [7,0,0,0,2,0,0,0,6], [0,6,0,0,0,0,2,8,0], [0,0,0,4,1,9,0,0,5], [0,0,0,0,8,0,0,7,9]]
   
def valid(grid,x,y,n):
    for i in range (9):
        if(grid[x][i] == n):
            return False
    return True
   
    for i in range(9):
        if(grid[y][i] == n):
            return False
    return True
   
    x //= 3
    y //= 3
    
    for i in range (3):
        for j in range(3):
            if(grid[x+i][y+j] == n):
                return False
    return True
       

def sudoku(grid):
    for x in range(9):
        for y in range(9):
            if(grid[x][y] == 0):
                for n in range(1,10):
                    if(valid(grid,x,y,n)):
                        grid[x][y] = n
                        result = sudoku(grid)
                        if result is not None:
                            return result
                        grid[x][y] = 0
                return
    return grid

我试图更改 x 和 y 的值,看看它是否检查每个网格中的 [x,y],但找不到问题。

python 数据结构 数独

评论


答:

0赞 ShadowRanger 9/7/2023 #1

函数在第一个循环后无条件返回(假设它不在该循环中)。函数的其余部分永远不会运行,因此只要第一个循环通过(确认数字不在行中),您就认为它有效,而无需选中列或 3x3 子框。删除该函数中除最后一个之外的所有功能。validTruereturn Falsereturn True

这仍然会给您留下其他问题(例如,您的第一个和第二个循环正在检查完全相同的东西,我怀疑您的意思是检查一个循环中的行和另一个循环中的列),但您会更接近。


关于简化代码的旁注:

一行是单个子行;你可以用该子中的简单成员资格测试完全替换第一个循环(同样的big-O,但在实践中,让Python在单个操作码中为你完成工作要快得多):listlist

if n in grid[x]:
    return False

第二个循环必须更复杂(因为您需要检查多行中每一行上的单个单元格),但您仍然可以通过直接迭代来避免如此多的索引,因此在修复后,它是:

for row in grid:     # Direct iteration, faster than index loop, gives a useful name,
    if row[y] == n:  # and simpler usage
        return False

评论

0赞 Armenta 9/7/2023
第一个和第二个循环检查相同,因为我有:grid[y][i] == n。当我应该翻转 y 和 i 时,对吗?
0赞 ShadowRanger 9/7/2023
@Armenta:是的。但正如我在更新的答案中提到的,这两个循环都可以大大简化,并消除一些混淆(通过完全摆脱大量手动索引)。
-1赞 Mohd Rayyan Khan 9/7/2023 #2

要解决此问题,请在“value”函数中调整 x 和 y 的计算:

grid2 = [[5,3,0,0,7,0,0,0,0], [6,0,0,1,9,5,0,0,0], [0,9,8,0,0,0,0,6,0], [8,0,0,0,6,0,0,0,3], [4,0,0,8,0,3,0,0,1], [7,0,0,0,2,0,0,0,6], [0,6,0,0,0,0,2,8,0], [0,0,0,4,1,9,0,0,5], [0,0,0,0,8,0,0,7,9]]

def valid(grid, x, y, n):
    for i in range(9):
        if grid[x][i] == n:
            return False

    for i in range(9):
        if grid[i][y] == n:
            return False

    # Calculate the starting row and column for the 3x3 subgrid
    x_start = (x // 3) * 3
    y_start = (y // 3) * 3

    for i in range(3):
        for j in range(3):
            if grid[x_start + i][y_start + j] == n:
                return False

    return True

def sudoku(grid):
    for x in range(9):
        for y in range(9):
            if(grid[x][y] == 0):
                for n in range(1,10):
                    if(valid(grid,x,y,n)):
                        grid[x][y] = n
                        result = sudoku(grid)
                        if result is not None:
                            return result
                        grid[x][y] = 0
                return
    return grid
sudoku(grid2)
0赞 Alain T. 9/7/2023 #3

我建议使用集合的更简洁的方法。数独棋盘中有 27 组数字,其中需要有互斥(唯一)的数字。每个位置都是其中 3 个组(行、列和块)的一部分。

因此,您可以首先创建一个函数,该函数将生成一组元组,表示给定数字在特定位置的 3 个条目(组、数字):

def sudoSet(r,c,n,base=3):
    side = base*base
    return { (r//base*base+c//base,n), (r+side,n), (c+2*side,n) }

然后,您可以使用此函数获取整个棋盘的已用排除项集(忽略包含零的位置):

def sudoSets(board,base=3):
    return set.union(*(sudoSet(r,c,n,base) 
                       for r,row in enumerate(board)
                       for c,n in enumerate(row) if n ))

使用这两个函数,检查是否可以将数字放置在给定位置成为一个简单的集合操作:

grid2 = [[5,3,0,0,7,0,0,0,0],
         [6,0,0,1,9,5,0,0,0],
         [0,9,8,0,0,0,0,6,0],
         [8,0,0,0,6,0,0,0,3],
         [4,0,0,8,0,3,0,0,1],
         [7,0,0,0,2,0,0,0,6],
         [0,6,0,0,0,0,2,8,0],
         [0,0,0,4,1,9,0,0,5],
         [0,0,0,0,8,0,0,7,9]]

used = sudoSets(grid2)
print(sudoSet(7,2,3).isdisjoint(used) ) # True, a 3 can be placed at 7,2
print(sudoSet(7,2,6).isdisjoint(used) ) # False, a 6 cannot be placed at 7,2 

通过这种方法,您可以轻松地跟踪电路板上数字的放置和移除,以便进行扩展和回溯:

# place 3 at 7,2:

grid2[7][2] = 3
used |= sudoSet(7,2,3)

# remove 3 placed at 7,2:

used -= sudoSet(7,2,grid2[7][2])
grid2[7][2] = 0

您还可以使用它来验证整个网格是否有效(所用集的大小应恰好是网格中非零数字数量的 3 倍):

isValid = len(used) == 3*sum(len(r)-r.count(0) for r in grid2)

使用这些函数和基于集合的方法,只需几行即可编写出蛮力求解器:

def sudoSolve(board):
    solution = [row.copy() for row in board]    # prepare solution
    used     = sudoSets(board)                  # track preset numbers
    empties  = [(r,c) for r,row in enumerate(board)
                      for c,n   in enumerate(row) if not n ] # pos to fill
    e,inc = 0,0
    while 0 <= e+inc < len(empties):
        e    += inc                             
        r,c   = empties[e]                      # position to assign
        if inc < 0:                             # unexclude previous attempt
            used -= sudoSet(r,c,solution[r][c])
        solution[r][c] += 1                     # try next value
        if solution[r][c] > 9:                  # All tried 
            solution[r][c] = 0                  # clear number for later
            inc = -1                            # backtrack position
            continue
        posSet = sudoSet(r,c,solution[r][c])    # position/number groups
        if posSet.isdisjoint(used):             # no conflict            
            used |= posSet                      # track exclusions
            inc = 1                             # next empty position
        else:
            inc = 0                             # try other at position
    return solution if inc >= 0 else []

输出:

print(*sudoSolve(grid2),sep="\n")

[5, 3, 4, 6, 7, 8, 9, 1, 2]
[6, 7, 2, 1, 9, 5, 3, 4, 8]
[1, 9, 8, 3, 4, 2, 5, 6, 7]
[8, 5, 9, 7, 6, 1, 4, 2, 3]
[4, 2, 6, 8, 5, 3, 7, 9, 1]
[7, 1, 3, 9, 2, 4, 8, 5, 6]
[9, 6, 1, 5, 3, 7, 2, 8, 4]
[2, 8, 7, 4, 1, 9, 6, 3, 5]
[3, 4, 5, 2, 8, 6, 1, 7, 9]
1赞 Armenta 9/7/2023 #4

我找到了答案,问题在于我的 for 循环来检查 3x3,因为我没有将 x 和 y 乘以 3。添加该修复了无法完全检查 3x3 的问题。我的代码的另一个问题是“if result is not None”语句。我删除了它,它将完全遍历数独,并且在调用 valid 的 if 语句中,我添加了另一个语句来检查数独是否没有任何 0。 以下是有效的代码:

def valid(grid,x,y,n):
#Checks row
if n in grid[x]:
    return False
#Checks columns
for row in grid:
   if(row[y] == n):
       return False

x //= 3
y //= 3
#Checks each square
for i in range (3):
    for j in range(3):
        if(grid[x*3+i][y*3+j] == n):
            return False
return True

def sudoku(grid):
for x in range(9):
    for y in range(9):
        if(grid[x][y] == 0):
            for n in range(1,10):
                if(valid(grid,x,y,n)):
                    grid[x][y] = n
                    result = sudoku(grid)
                    if np.count_nonzero(result) == 81:
                        return result
                        return result
                    grid[x][y] = 0
            return grid
return grid