[英]Sudoku Puzzle with boxes containing square numbers
兩天前,我收到了一個我試圖用 Python 3 解決的數獨問題。我被告知確實存在一個解決方案,但我不確定是否存在多個解決方案。
問題如下: 一個 9x9 的數獨網格是完全空的。 然而,它確實包含彩色框,並且在這些框內,數字的總和必須是平方數。 除此之外,正常的數獨規則適用。
這里的問題不是解決數獨謎題,而是生成一個滿足彩色框規則的可行謎題。
我的策略
使用 numpy 數組,我將網格划分為 81 個索引,這些索引可以重新排列為 9x9 網格。
import numpy as np
print(np.array([i for i in range(81)]).reshape((9, 9)))
->
[[ 0 1 2 3 4 5 6 7 8]
[ 9 10 11 12 13 14 15 16 17]
[18 19 20 21 22 23 24 25 26]
[27 28 29 30 31 32 33 34 35]
[36 37 38 39 40 41 42 43 44]
[45 46 47 48 49 50 51 52 53]
[54 55 56 57 58 59 60 61 62]
[63 64 65 66 67 68 69 70 71]
[72 73 74 75 76 77 78 79 80]]
這是一個包含所有索引塊的列表。
boxes = [[44, 43, 42, 53],[46, 47, 38],[61, 60],[69, 70],[71, 62],
[0, 9, 18],[1, 10, 11, 20],[2, 3, 12],[4, 13, 14],[5, 6],
[7, 8],[17, 26, 35],[21, 22, 23],[15, 16, 24, 25, 34],
[27, 36, 37],[19, 28, 29],[45, 54],[55, 56],[63, 64, 65],
[72, 73, 74],[57, 66, 75 ],[58, 59, 67, 68],[76, 77],[78, 79, 80]]
正如您從圖片或上面的數組中看到的,這些盒子被排列成 2、3、4 或 5 個塊(8 個二、12 個三、3 個四、1 個五)。 我還注意到一個盒子可以包含多個數字而不會違反任何數獨規則,但一個數字中只能有 2 個。 鑒於這些信息,最大可能的平方將是 36,因為 9+9+8+7+6 = 39,因此一個塊的總和永遠不會達到 49。 找出列表的總和是否包含一個平方數,我做了以下功能:
def isSquare(array):
if np.sum(array) in [i**2 for i in range(1,7)]:
return True
else:
return False
為了找出列表是否包含正確數量的重復項,即只有一個數字的多個重復項,我做了以下功能:
def twice(array):
counter = [0]*9
for i in range(len(array)):
counter[array[i]-1]+=1
if 3 in counter:
return False
if counter.count(2)>1:
return False
return True
現在,給定數字 1-9,如果列表必須求和成一個平方數,那么列表的解決方法是有限的。 使用itertools ,我可以找到解決方案,將它們分成一個數組,其中索引 0 包含兩個塊,索引 1 包含三個塊,依此類推。
from itertools combinations_with_replacement
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
然而,這些列表的任何排列都是“平方問題”的可行解決方案。 再次使用itertools ,可能的盒子總數(沒有數獨規則)總和為 8782。
from itertools import permutations
def find_squares():
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
s = []
for item in solutions:
d=[]
for arr in item:
for k in permutations(arr):
d.append(list(k))
s.append(d)
return s # 4-dimensional array, max 2 of each
solutions = find_squares()
total = sum([len(i) for i in solutions])
print(total)
-> 8782
這應該足以實現決定棋盤是否合法的功能,即行、列和框僅包含 1-9 位數字中的每一個。 我的實現:
def legal_row(arr):
for k in range(len(arr)):
values = []
for i in range(len(arr[k])):
if (arr[k][i] != 0):
if (arr[k][i] in values):
return False
else:
values.append(arr[k][i])
return True
def legal_column(arr):
return legal_row(np.array(arr, dtype=int).T)
def legal_box(arr):
return legal_row(arr.reshape(3,3,3,3).swapaxes(1,2).reshape(9,9))
def legal(arr):
return (legal_row(arr) and legal_column(arr) and legal_box(arr))
運行時的困難
一種直接的方法是檢查每個塊的每個組合。 我已經這樣做了,並產生了幾個可行的問題,但是我的算法的復雜性使得這需要很長時間。
相反,我試圖隨機化一些屬性:塊的順序和解決方案的順序。 使用這個,我限制了嘗試的次數,並檢查了一個解決方案是否可行:
attempts = 1000
correct = 0
possibleBoards = []
for i in range(1, attempts+1):
board = np.zeros((9, 9), dtype=int)
score = 0
shapes = boxes
np.random.shuffle(shapes)
for block in shapes:
new_board = board
new_1d = board.reshape(81)
all_sols = solutions[len(block)-2]
np.random.shuffle(all_sols)
for sols in all_sols:
#print(len(sols))
new_1d[block] = sols
new_board = new_1d.reshape((9, 9))
if legal(new_board):
board = new_board
score+=1
break
confirm = board.reshape(81)
#solve(board) # Using my solve function, not important here
# Note that without it, correct would always be 0 as the middle of the puzzle has no boxes
confirm = board.reshape(81)
if (i%1000==0 or i==1):
print("Attempt",i)
if 0 not in confirm:
correct+=1
print(correct)
possibleBoards.append(board)
在上面的代碼中,變量 score 是指算法在嘗試期間可以找到多少塊。 變量正確是指可以完成多少生成的數獨板。 如果您對它在 700 次嘗試中的表現感興趣,這里有一些統計數據(這是一個直方圖,x 軸代表分數,y 軸代表在這 700 次嘗試中每個分數出現的數量)。
我需要什么幫助
我正在努力尋找一種可行的方法來解決這個問題,它實際上可以在有限的時間內運行。 我將不勝感激有關使我的某些代碼更快或更好的任何提示,對問題的不同方法的任何想法,問題的任何解決方案,或有關與此問題相關的 Python/Numpy 的一些有用提示。
這是我將使用 SMT 求解器的地方。 他們比人們認為的要強大得多。 如果您能想到的最佳算法本質上是蠻力,請嘗試使用求解器。 只需列出您的約束並運行它,幾秒鍾內就會給出您獨特的答案:
278195436
695743128
134628975
549812763
386457291
721369854
913286547
862574319
457931682
使用的代碼(和坐標參考圖像):
import z3
letters = "ABCDEFGHI"
numbers = "123456789"
boxes = """
A1 A2 A3
B1 B2 C2 C3
C1 D1 D2
E1 E2 F2
F1 G1
H1 I1
G2 H2 G3 H3 H4
I2 I3 I4
B3 B4 C4
D3 E3 F3
A4 A5 B5
C5 B6 C6
G5 H5 I5 I6
A6 A7
B7 C7
D7 D8 D9
E7 E8 F7 F8
G7 H7
I7 I8
A8 B8 C8
G8 H8
A9 B9 C9
E9 F9
G9 H9 I9
"""
positions = [letter + number
for letter in letters
for number in numbers]
S = {pos: z3.Int(pos) for pos in positions}
solver = z3.Solver()
# Every symbol must be a number from 1-9.
for symbol in S.values():
solver.add(z3.Or([symbol == i for i in range(1, 10)]))
# Every row value must be unique.
for row in numbers:
solver.add(z3.Distinct([S[col + row] for col in letters]))
# Every column value must be unique.
for col in letters:
solver.add(z3.Distinct([S[col + row] for row in numbers]))
# Every block must contain every value.
for i in range(3):
for j in range(3):
solver.add(z3.Distinct([S[letters[m + i * 3] + numbers[n + j * 3]]
for m in range(3)
for n in range(3)]))
# Colored boxes.
for box in boxes.split("\n"):
box = box.strip()
if not box: continue
boxsum = z3.Sum([S[pos] for pos in box.split()])
solver.add(z3.Or([boxsum == 1, boxsum == 4, boxsum == 9,
boxsum == 16, boxsum == 25, boxsum == 36]))
# Print solutions.
while solver.check() == z3.sat:
model = solver.model()
for row in numbers:
print("".join(model.evaluate(S[col+row]).as_string()
for col in letters))
print()
# Prevent next solution from being equivalent.
solver.add(z3.Or([S[col+row] != model.evaluate(S[col+row])
for col in letters
for row in numbers]))
創建了一個名為 Boxes 的列表,其中包含 9 個元素,每個元素都是另一個列表。 這 9 個列表對應於 9 個盒子中的每一個,每個列表都包含元組作為元素,該元素具有該盒子中每個方塊的行和列索引。 以與以下類似的方式顯式輸入值會產生相同的效果(但會浪費時間):
# The boxes list is created, with the row and column index of each square in each box Boxes = [ [(3*i+k+1, 3*j+l+1) for k in range(3) for l in range(3)] for i in range(3) for j in range(3) ]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.