慶應入試で出題された数独の類似問題のWindokuをPythonで解いてみました。
+-----+-----+-----+
|3 9 ・|・ ・ ・|・ ・ 4|
| +---+-+ +-+---+ |
|・|・ ・|9|・|・|・ 6|・|
| | | | | | | |
|・|・ ・|3|1|・|5 ・|9|
+-+---+-+-+-+---+-+
|2|7 ・|・|・|・|9 ・|・|
| +---+-+ +-+---+ |
|8 ・ ・|・ 2 9|・ ・ ・|
| +---+-+ +-+---+ |
|・|・ 9|・|・|7|3 ・|・|
+-+---+-+-+-+---+-+
|・|・ 1|2|9|6|・ 8|・|
| | | | | | | |
|・|・ 5|・|・|・|・ 9|・|
| +---+-+ +-+---+ |
|9 6 ・|・ ・ ・|7 ・ ・|
+-----+-----+-----+
前回の数独のプログラムに、「灰色の4個の3×3のマスに1〜9までの数字がすべて現れる」かチェックする部分を書き加えただけです。(^_^;
この「Windoku」は、「Hypersudoku」とか「Four-Box Sudoku」とか「NRC-Sudoku」とも呼ばれているようです。(^_^;
ちなみに、通常の数独のルールだけで解くと、156個の解が見つかって、解は確定しません。
● Windoku1.py
from time import time
import itertools
sQ = '''
3 9 .|. . .|. . 4
+---+-+ +-+---+
.|. .|9|.|.|. 6|.
.|. .|3|1|.|5 .|9
-+---+-+-+-+---+-
2|7 .|.|.|.|9 .|.
+---+-+ +-+---+
8 . .|. 2 9|. . .
+---+-+ +-+---+
.|. 9|.|.|7|3 .|.
-+---+-+-+-+---+-
.|. 1|2|9|6|. 8|.
.|. 5|.|.|.|. 9|.
+---+-+ +-+---+
9 6 .|. . .|7 . .
'''
def toStr(lst):
return '[%s]'%','.join(['%s'%x for x in lst])
def isDupClm(n,li2D):
m = len(li2D)
for i in range(m):
for j in range(n):
if li2D[n][i]==li2D[j][i]: return True
return False
def isDupBlk(n,li2D):
m = n//3
for k in range(3):
K = 3*k
li = [li2D[i][j] for i in range(3*m,n) for j in range(K,K+3)]
for i in range(3):
if li2D[n][K+i] in li: return True
return False
def getBlk(m,n,li2D):
m,n = m//3,n//3
li = [li2D[i][j] for i in range(3*m,3*m+3) \
for j in range(3*n,3*n+3) if li2D[i][j]!=0]
return li
R = range(1,10)
rowFlg = [[] for i in range(9)]
colFlg = [[] for i in range(9)]
blkFlg = [[] for i in range(9)]
def mkFlgs(la):
for i in range(9):
rowFlg[i] = [la[i][k] for k in range(9) if la[i][k]!=0]
for j in range(9):
colFlg[j] = [la[k][j] for k in range(9) if la[k][j]!=0]
for i in range(0,9,3):
for j in range(0,9,3):
blkFlg[i+j//3] = getBlk(i,j,la)
def mkCandidateTbl(la):
lb = [[[] for i in range(9)] for j in range(9)]
mkFlgs(la)
for i in range(9):
for j in range(9):
if la[i][j]!=0:
lb[i][j]=[la[i][j]]
else:
li = rowFlg[i]+colFlg[j]+blkFlg[i//3*3+j//3]
lb[i][j] = sorted(set(R)-set(li))
return lb
def decideCell(la,lb):
li = la[:]
fd = False
for i in range(9):
for j in range(9):
if la[i][j]!=0: continue
if len(lb[i][j])==1:
li[i][j] = lb[i][j][0]
fd = True
if not fd: li = []
return li
def decideClm(la,lb):
li = la[:]
fd = False
for j in range(9):
for m in tuple(set(R)-set(colFlg[j])):
c = k = 0
flg = True
for i in range(9):
if la[i][j]!=0: continue
if m in lb[i][j]:
c+=1; k = i
if c> 1: break
else: flg = False
if flg: continue
if c==1:
li[k][j] = m
fd = True
if not fd: li = []
return li
def decideRow(la,lb):
li = la[:]
fd = False
for i in range(9):
for m in tuple(set(R)-set(rowFlg[i])):
c = k = 0
flg = True
for j in range(9):
if la[i][j]!=0: continue
if m in lb[i][j]:
c+=1; k = j
if c> 1: break
else: flg = False
if flg: continue
if c==1:
li[i][k] = m
fd = True
if not fd: li = []
return li
def decideBlk(la,lb):
li = la[:]
fd = False
for i in range(0,9,3):
for j in range(0,9,3):
for m in tuple(set(R)-set(blkFlg[i//3*3+j//3])):
c = ib = jb = 0
flg = True
for k in range(9):
if la[i+k//3][j+k%3]!=0: continue
if m in lb[i+k//3][j+k%3]:
c+=1; ik,jk = i+k//3,j+k%3
if c> 1: break
else: flg = False
if flg: continue
if c==1:
li[ik][jk] = m
fd = True
if not fd: li = []
return li
def mkPerm(la):
perm = [[] for i in range(9)]
while True:
flg = True
b = mkCandidateTbl(la)
li = decideCell(la,b)
if li!=[]:
la = li[:]; flg = False
b = mkCandidateTbl(la)
li = decideClm(la,b)
if li!=[]:
la = li[:]; flg = False
b = mkCandidateTbl(la)
li = decideRow(la,b)
if li!=[]:
la = li[:]; flg = False
b = mkCandidateTbl(la)
li = decideBlk(la,b)
if li!=[]:
la = li[:]; flg = False
b = mkCandidateTbl(la)
if flg: break
for k in range(9):
li = b[k]
r = list(set(R)-set([n for n in la[k] if n!=0]))
for p in itertools.permutations(r):
p = list(p)
for i in range(9):
if la[k][i]!=0:
p[i:i] = [la[k][i]]
flg = True
for i in range(9):
if la[k][i]!=0: continue
if p[i] not in li[i]: break
else: flg = False
if flg: continue
perm[k].append(p)
return perm
def solver(sq):
m = [[0 for i in range(9)] for j in range(9)]
a = [[0 for i in range(9)] for j in range(9)]
sq = sq.replace('.','0')
lq = [c for c in sq if c in '0123456789']
for i in range(9):
for j in range(9):
a[i][j] = int(lq[9*i+j])
Perm = mkPerm(a)
ans = []
for m[0] in Perm[0]:
for m[1] in Perm[1]:
if isDupClm(1,m): continue
if isDupBlk(1,m): continue
for m[2] in Perm[2]:
if isDupClm(2,m): continue
if isDupBlk(2,m): continue
for m[3] in Perm[3]:
if isDupClm(3,m): continue
for m[4] in Perm[4]:
if isDupClm(4,m): continue
if isDupBlk(4,m): continue
for m[5] in Perm[5]:
if isDupClm(5,m): continue
if isDupBlk(5,m): continue
for m[6] in Perm[6]:
if isDupClm(6,m): continue
for m[7] in Perm[7]:
if isDupClm(7,m): continue
if isDupBlk(7,m): continue
for i in range(9):
li = [m[j][i] for j in range(8)]
m[8][i] = 45-sum(li)
if isDupBlk(8,m): continue
flg = True
for i in range(9):
if a[8][i]==0: continue
if a[8][i]!=m[8][i]: break
else: flg = False
if flg: continue
flg = True
for k in range(4):
p,q = 1+k//2*4,1+k%2*4
li = [m[i][j] for i in range(p,p+3) \
for j in range(q,q+3)]
if len(set(li))!=9: break
else: flg = False
if flg: continue
pass
ans.append(m[:][:])
return ans
return ans
def main():
tm = time()
a = solver(sQ)
for i in range(len(a)):
for j in range(9):
print(toStr(a[i][j]))
print('')
print("Runtime : %.3f [sec]"%(time()-tm))
if __name__ == '__main__':
main()
●実行結果
[3,9,7,8,6,5,1,2,4]
[5,1,4,9,7,2,8,6,3]
[6,2,8,3,1,4,5,7,9]
[2,7,6,5,4,3,9,1,8]
[8,5,3,1,2,9,6,4,7]
[1,4,9,6,8,7,3,5,2]
[7,3,1,2,9,6,4,8,5]
[4,8,5,7,3,1,2,9,6]
[9,6,2,4,5,8,7,3,1]
Runtime : 0.316 [sec]
※参考URL
●慶應SFCの入試で出題されたWindokuと呼ばれる数独に似た問題の考察
●Cross+A :: 数独 (Sudoku) > ウィンドク(Windoku)