我目前正在尝试用Python编写一个递归的数独求解算法。我为Sudokus创建了一个类,它包含一些帮助我操作数独网格的方法。
这是我的代码:
class Sudoku:
def __init__(self, input, tokens = None):
self.data = {}
if tokens is None:
self.tokens = list(range(1, 10))
else:
self.tokens = tokens
assert len(self.tokens) == 9
if type(input) == dict:
self.data = input
else:
for i, cell in enumerate(input):
if cell in self.tokens:
self.data[i % 9, i // 9] = cell
def __repr__(self):
string = ''
canvas = [['.'] * 9 for line in range(9)]
for (col, row), cell in self.data.items():
canvas[row][col] = str(cell)
for y, row in enumerate(canvas):
if not y % 3:
string += "+-------+-------+-------+\n"
string += '| {} | {} | {} |\n'.format(' '.join(row[:3]), ' '.join(row[3:6]), ' '.join(row[6:]))
string += "+-------+-------+-------+"
return string
@classmethod
def sq_coords(cls, cell_x, cell_y):
#returns all coordinates of cells in the same square as the one in (cell_x, cell_y)
start_x, start_y = cell_x // 3 * 3, cell_y // 3 * 3
for dx in range(3):
for dy in range(3):
yield (start_x +dx, start_y + dy)
def copy(self):
return Sudoku(self.data)
def clues(self, cell_x, cell_y):
assert not self.data.get((cell_x, cell_y))
allowed = set(self.tokens)
#Remove all numbers on the same row, column and square as the cell
for row in range(9):
allowed.discard(self.data.get((cell_x, row)))
for col in range(9):
allowed.discard(self.data.get((col, cell_y)))
for coords in self.sq_coords(cell_x, cell_y):
allowed.discard(self.data.get(coords))
return allowed
def get_all_clues(self):
clues = {}
for row in range(9):
for col in range(9):
if not self.data.get((col, row)):
clues[col, row] = self.clues(col, row)
return clues
def fill_singles(self):
still_going = True
did_something = False
while still_going:
still_going = False
for (col, row), clues in self.get_all_clues().items():
if len(clues) == 1:
still_going = True
did_something = True
self.data[col, row] = clues.pop()
return did_something
def place_finding(self):
still_going = True
did_something = False
while still_going:
still_going = False
for token in self.tokens:
for group in self.get_groups():
available_spots = [coords for coords, cell in group.items() if cell == None and token in self.clues(*coords)]
if len(available_spots) == 1:
self.data[available_spots.pop()] = token
still_going = True
did_something = True
return did_something
def fill_obvious(self):
still_going = True
while still_going:
a = self.fill_singles()
b = self.place_finding()
still_going = a or b
def get_groups(self):
for y in range(9):
yield {(x, y) : self.data.get((x, y)) for x in range(9)}
for x in range(9):
yield {(x, y) : self.data.get((x, y)) for y in range(9)}
for n in range(9):
start_x, start_y = n % 3 * 3, n // 3 * 3
yield {(x, y) : self.data.get((x, y)) for x, y in self.sq_coords(start_x, start_y)}
def is_valid(self):
for group in self.get_groups():
if any([list(group.values()).count(token) > 1 for token in self.tokens]):
return False
return True
def is_solved(self):
return self.is_valid() and len(self.data) == 9 * 9
def solve(sudoku):
def loop(su):
if su.is_solved():
print(su)
elif su.is_valid():
su.fill_obvious()
print(su)
for coords, available_tokens in sorted(su.get_all_clues().items(), key=lambda kv: len(kv[1])):
for token in available_tokens:
new_su = su.copy()
new_su.data[coords] = token
loop(new_su)
loop(sudoku)
with open('input.txt') as f:
numbers = ''
for i, line in enumerate(f):
if i >= 9:
break
numbers += line.rstrip().ljust(9)
s = Sudoku(numbers, tokens='123456789')
print(s)
solve(s)
print(s)
很抱歉,如果这看起来很乱,但我宁愿给你我所拥有的一切,而不仅仅是一些可能包含或不包含问题的数据。
正如你所看到的,它所做的第一件事是使用fill_singles方法填充Sudoku只有100%确定的数字(填充每个只能用数字x填充的单元格,例如其他8种可能性在其行中,列或块)和place_finding(检查所有标记,看看组中是否只有一个空格 - 行,列或块 - 它们可以适合的位置)。它遍历这两者,直到无法完成任何事情。
然后,它会尝试在空间最小的空间中尝试使用相同的方法解决新制作的网格。这就是我的问题所在。目前,出于调试目的,程序会打印出有效时遇到的网格(组中没有相同的数字两次)。实际上这就是我想要的。但它不能像这样工作;有了这个输入:
+-------+-------+-------+
| . . . | . 2 6 | . . 4 |
| . . . | 7 9 . | 5 . . |
| . . . | . . . | 9 1 . |
+-------+-------+-------+
| . 8 . | 1 . . | . . . |
| 2 3 6 | . . . | 1 8 5 |
| . . . | . . 3 | . 7 . |
+-------+-------+-------+
| . 4 7 | . . . | . . . |
| . . 3 | . 7 8 | . . . |
| 5 . . | 6 3 . | . . . |
+-------+-------+-------+
它输出这样的网格,显然无效:
+-------+-------+-------+
| 1 4 9 | 3 2 6 | 7 3 4 |
| 3 2 4 | 7 9 1 | 5 1 8 |
| 3 7 5 | 3 2 4 | 9 1 2 |
+-------+-------+-------+
| 7 8 6 | 1 1 4 | 3 2 5 |
| 2 3 6 | 9 4 7 | 1 8 5 |
| 4 1 7 | 2 3 3 | 6 7 4 |
+-------+-------+-------+
| 2 4 7 | 1 1 3 | 5 4 6 |
| 1 3 3 | 4 7 8 | 2 5 1 |
| 5 5 4 | 6 3 2 | 1 3 7 |
+-------+-------+-------+
我真的不明白为什么它允许这样的网格通过is_valid测试,特别是当我手动输入上面的网格时,它没有通过测试:
>>> s
+-------+-------+-------+
| 1 4 9 | 3 2 6 | 7 3 4 |
| 3 2 4 | 7 9 1 | 5 1 8 |
| 3 7 5 | 3 2 4 | 9 1 2 |
+-------+-------+-------+
| 7 8 6 | 1 1 4 | 3 2 5 |
| 2 3 6 | 9 4 7 | 1 8 5 |
| 4 1 7 | 2 3 3 | 6 7 4 |
+-------+-------+-------+
| 2 4 7 | 1 1 3 | 5 4 6 |
| 1 3 3 | 4 7 8 | 2 5 1 |
| 5 5 4 | 6 3 2 | 1 3 7 |
+-------+-------+-------+
>>> s.is_valid()
False
任何人都可以在我的代码中看到我没有注意到的错误吗?对不起,我并不是特定的,但是我试着查看我的每一段代码,似乎找不到任何东西。
对于@AnandSKatum:
26 4
79 5
91
8 1
236 185
3 7
47
3 78
5 63
答案 0 :(得分:0)
这是我为你写的更好的实现
from numpy.lib.stride_tricks import as_strided
from itertools import chain
import numpy
def block_view(A, block= (3, 3)):
"""Provide a 2D block view to 2D array. No error checking made.
Therefore meaningful (as implemented) only for blocks strictly
compatible with the shape of A."""
# simple shape and strides computations may seem at first strange
# unless one is able to recognize the 'tuple additions' involved ;-)
shape= (A.shape[0]/ block[0], A.shape[1]/ block[1])+ block
strides= (block[0]* A.strides[0], block[1]* A.strides[1])+ A.strides
return chain.from_iterable(as_strided(A, shape= shape, strides= strides))
def check_board(a):
"""
a is a 2d 9x9 numpy array, 0 represents None
"""
for row,col,section in zip(a,a.T,block_view(a,(3,3))):
s = list(chain.from_iterable(section))
if any(sum(set(x)) != sum(x) for x in [row,col,s]):
return False
return True
a = numpy.array(
[
[9,8,7,6,5,4,3,2,1],
[2,1,5,0,0,0,0,0,0],
[3,6,4,0,0,0,0,0,0],
[4,2,0,0,0,0,0,0,0],
[5,3,0,0,0,0,0,0,0],
[6,7,0,0,0,0,0,0,0],
[7,0,0,0,0,0,0,0,0],
[8,0,0,0,0,0,0,0,0],
[1,0,0,0,0,0,0,0,0],
]
)
print check_board(a)