介紹如何使用 Python 產生數獨題目,並以程式自動解題,找出數獨答案。
產生數獨題目
這是產生數獨題目的 Python 函數,他會產生一個 9×9 的矩陣,其中的 0
代表尚未填入數字的空格:
# 產生數獨題目 def generate_board(): base = 3 side = base*base # pattern for a baseline valid solution def pattern(r,c): return (base*(r%base)+r//base+c)%side # randomize rows, columns and numbers (of valid base pattern) from random import sample def shuffle(s): return sample(s,len(s)) rBase = range(base) rows = [ g*base + r for g in shuffle(rBase) for r in shuffle(rBase) ] cols = [ g*base + c for g in shuffle(rBase) for c in shuffle(rBase) ] nums = shuffle(range(1,base*base+1)) # 隨機產生的數獨數字盤 board = [ [nums[pattern(r,c)] for c in cols] for r in rows ] # 移除部分數字,產生數獨題目 squares = side*side empties = squares * 3//4 for p in sample(range(squares),empties): board[p//side][p%side] = 0 return board
執行此函數之後,就會隨機產生一個數獨題目數字盤:
# 產生數獨題目
board = generate_board()
我們也可以透過手動指定的方式,建立數獨題目數字盤:
# 手動指定數獨題目 board = [[8, 0, 0, 0, 0, 5, 4, 3, 0], [0, 0, 0, 0, 0, 8, 0, 0, 0], [5, 0, 7, 0, 4, 0, 9, 0, 0], [0, 6, 0, 0, 7, 0, 0, 4, 0], [0, 0, 0, 0, 6, 0, 7, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [6, 0, 0, 9, 0, 7, 0, 0, 0], [0, 0, 0, 0, 3, 0, 0, 0, 0], [0, 0, 9, 0, 0, 0, 3, 0, 0]]
顯示數獨數字盤
為了讓數獨數字盤更好閱讀,這裡提供了兩個顯示數獨數字盤用的函數:
# 顯示簡單版數獨數字盤 def print_board(bo): for i in range(len(bo)): if i % 3 == 0 and i != 0: print("------+-------+------") for j in range(len(bo[0])): if j % 3 == 0 and j != 0: print("| ", end="") if j == 8: print(bo[i][j]) else: print(str(bo[i][j]) + " ", end="") # 顯示豪華版數獨數字盤 def print_board2(board): def expandLine(line): return line[0]+line[5:9].join([line[1:5]*(base-1)]*base)+line[9:13] line0 = expandLine("╔═══╤═══╦═══╗") line1 = expandLine("║ . │ . ║ . ║") line2 = expandLine("╟───┼───╫───╢") line3 = expandLine("╠═══╪═══╬═══╣") line4 = expandLine("╚═══╧═══╩═══╝") symbol = " 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" nums = [ [""]+[symbol[n] for n in row] for row in board ] print(line0) for r in range(1,side+1): print( "".join(n+s for n,s in zip(nums[r-1],line1.split("."))) ) print([line2,line3,line4][(r%side==0)+(r%base==0)])
將數獨數字盤傳入函數,就會印出排版好的數字盤:
# 顯示簡單版數獨數字盤
print_board(board)
8 0 0 | 0 0 5 | 4 3 0 0 0 0 | 0 0 8 | 0 0 0 5 0 7 | 0 4 0 | 9 0 0 ------+-------+------ 0 6 0 | 0 7 0 | 0 4 0 0 0 0 | 0 6 0 | 7 1 0 0 0 0 | 0 0 0 | 0 0 0 ------+-------+------ 6 0 0 | 9 0 7 | 0 0 0 0 0 0 | 0 3 0 | 0 0 0 0 0 9 | 0 0 0 | 3 0 0
這是豪華版的數獨數字盤:
# 顯示豪華版數獨數字盤
print_board2(board)
╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗ ║ 8 │ │ ║ │ │ 5 ║ 4 │ 3 │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ │ │ ║ │ │ 8 ║ │ │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ 5 │ │ 7 ║ │ 4 │ ║ 9 │ │ ║ ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣ ║ │ 6 │ ║ │ 7 │ ║ │ 4 │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ │ │ ║ │ 6 │ ║ 7 │ 1 │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ │ │ ║ │ │ ║ │ │ ║ ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣ ║ 6 │ │ ║ 9 │ │ 7 ║ │ │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ │ │ ║ │ 3 │ ║ │ │ ║ ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢ ║ │ │ 9 ║ │ │ ║ 3 │ │ ║ ╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝
數獨自動解題
以下是數獨自動解題用的幾個函數:
# 自動解題 def solve(bo): found = find_empty(bo) if not found: return True else: row, col = found for i in range(1, 10): if valid(bo, i, (row, col)): bo[row][col] = i if solve(bo): return True bo[row][col] = 0 return False # 檢查指定位置的數字是否合理 def valid(bo, num, pos): # 檢查同一列(Row)內是否重複 for i in range(len(bo[0])): if bo[pos[0]][i] == num and pos[1] != i: return False # 檢查行(Column)內是否重複 for i in range(len(bo)): if bo[i][pos[1]] == num and pos[0] != i: return False # 檢查小方格內是否重複 box_x = pos[1] // 3 box_y = pos[0] // 3 for i in range(box_y*3, box_y*3 + 3): for j in range(box_x * 3, box_x*3 + 3): if bo[i][j] == num and (i,j) != pos: return False return True # 尋找一個尚未解出的位置 def find_empty(bo): for i in range(len(bo)): for j in range(len(bo[0])): if bo[i][j] == 0: # 傳回位置 (row, column) return (i, j) return None
使用這個 Python 程式自動解題:
# 自動解題 solve(board) # 輸出數獨答案數字盤 print_board(board)
8 1 2 | 7 9 5 | 4 3 6 4 9 6 | 3 1 8 | 2 5 7 5 3 7 | 2 4 6 | 9 8 1 ------+-------+------ 2 6 1 | 5 7 3 | 8 4 9 3 4 5 | 8 6 9 | 7 1 2 9 7 8 | 4 2 1 | 5 6 3 ------+-------+------ 6 5 3 | 9 8 7 | 1 2 4 7 8 4 | 1 3 2 | 6 9 5 1 2 9 | 6 5 4 | 3 7 8