Python秒解數獨

2021-03-02 俊逸立新

封面Image by JeromeWare from Pixabay 

數獨起源於18世紀初瑞士數學家歐拉等人研究的拉丁方陣(Latin Square)。19世紀80年代,有美國人根據這種拉丁方陣發明了數獨的雛形。20世紀70年代,出現了公認的數獨最早的見報版本。1984年一位日本學者將其介紹到了日本,起名為「數字は獨身に限る」(すうじはどくしんにかぎる),後改名為「數獨」(すうどく),其中「數」(すう)是數字的意思,「獨」(どく)是唯一的意思。

數獨遊戲如下例:

 

在空白格內填1-9,保證每個豎排九格,每個橫排九格,每個粗線九宮格都有數字1-9。

完成:

數獨存在很多進階解法,高階解法,例如:X-Wing,XY-Chian之類。目前已經證明,給定的提示數(一開始標黑色的那些數字)如果少於等於16個,則一定存在多個解(多於16個也不一定是唯一解,還要看給定的提示數的位置)。

數獨的電腦程式解法

電腦程式最簡單的數獨解決辦法就是單純回溯法,即:從第一個空格開始,嘗試每一個數字,直到得到最後結果。這種方法存在優化的空間----剪枝,可以大幅縮減計算量。包括已經在圍棋上一騎絕塵超越人類的AlphaZero(AlphaGo增強版),也需要採用這種算法。通過編程X-Wings、XY-Chain之類的數獨進階、高階解法來解數獨,也不是不可以,但它存在兩個問題

由於進階、高階解法的複雜性,導致代碼複雜程度急劇上升,使得編寫代碼變得非常困難;

同時,就算完成高階解法的程序編寫,也不能保證這就是萬能解決方案。

Python解法思路

在Python中,有一個擅長處理矩陣的包,Numpy。這個包是用C語言編寫的,所以運算效率高於純Python代碼,同時矩陣切片非常方便。

用一個9個元素的向量代表1-9的可能性,有可能,記為1,不可能,記為0。

例:

[0 0 0 1 0 0 0 0 0] => 只可能為4

[1 1 0 0 0 1 0 0 1] => 可能為1、2、6、9

[1 1 1 1 1 1 1 1 1] = > 1至9都有可能

[0 0 0 0 0 0 0 0 0] = > 沒有任何一個數字可能,解法錯誤

將橫豎都為9行的數獨(9x9的二維數組),每一個方格替換為一個9元素向量,則數獨就變成了9x9x9的三維數組。

而當所有向量都只含有一個1,八個0的時候,就得到了數獨的解。

那麼所謂同一數字在橫排、豎排、所在九宮格內只能出現一次

在numpy裡應該怎麼理解?

由於已經確定了紅框1的第一位是1,所以除了紅框以外,所有紅框相關的格(也就是有顏色的那些格),第一位都必須是 0

根據以上的規律,可以將解法建立如下:

1、根據給定唯一數,設置初始三維數組;

2、根據唯一數情況,剔除相關格(唯一數所在的橫排、豎排、九宮格)的可能性;

3、重複第2步,直至無法再進一步剔除;

4、任選一格(當然最好是只有兩個數字可能性的格子),假設為其中一個數字;

5、重複第2步,直至無法再進一步剔除,或出現錯誤;

6、如果第5步出現錯誤,則退回第4步,假設該格為其他可能的數字;

7、如果第5步是無法再進一步剔除,則重複第4步;

8、直到出現所有向量都只含有一個1的時候,表示得到一個解

代碼

導入numpy模塊

設置一個類,這個類只儲存初始數獨問題,以及所有該數獨的答案(如果不止一個的話),可以列印數獨,以及所有答案

class Sudoku:    def __init__(self):        self.sudoku = np.zeros([9, 9, ], dtype=np.int8)        self.sudoku_final = []
def start_puzzle(self, sequence): if isinstance(sequence, str): if len(sequence) == 81: for index, number in enumerate(sequence): num = int(number) if num > 0: self.sudoku[index // 9][index - index // 9 * 9] = num else: raise Exception("sequence lenth is not 81") else: raise Exception("sequence type error, need 81 lenth string of numbers") return
     def print_sudoku(self, sudoku=False): if isinstance(sudoku, bool): sudoku = self.sudoku for index, array in enumerate(sudoku): if index % 3 == 0: print('+-' * 3) item = array.astype(str) print(f'{" ".join(item[:3])}| {" ".join(item[3:6])}| {" ".join(item[6:])}|') return
def print_answers(self): if len(self.sudoku_final) == 0: print('Failed to find any solution, sorry~') return elif len(self.sudoku_final) == 1: print('There is only 1 solution') else: print(f'There are {len(self.sudoku_final)} solutions') for solution in self.sudoku_final: self.print_sudoku(solution) return

設置一個類,這個類相當於草稿紙,用於:

class Draft:    def __init__(self):        self.sudoku = np.ones([9, 9, 9, ], dtype=np.int8)        self.alter = False
def initiation(self, s): for y, array in enumerate(s.sudoku): for x, number in enumerate(array): if number != 0: self.sudoku[y][x] = np.zeros([9, ], dtype='int8') self.sudoku[y, x, number - 1] = 1 return
def duplicate(self): new_draft = Draft() new_draft.sudoku = self.sudoku + 1 - 1 return new_draft
    def add_solution(self, sudoku): """the result must be checked""" index = np.where(self.sudoku == 1)[2] index += 1 solution = index.flatten() solution.resize((9, 9)) sudoku.sudoku_final.append(solution) return
def finish_check(self): draft_array = np.sum(self.sudoku, axis=2).ravel() if (draft_array == 1).all(): return True else: return False
def _set_origin(self): origin = self.sudoku + 1 origin -= 1 return origin
def alter_check(self, origin): return (origin != self.sudoku).any()
def last_number(self): matrix = np.sum(self.sudoku, axis=2) if (matrix == 0).any(): return False origin = self._set_origin() index = np.where(matrix == 1) for i in range(len(index[0])): y = index[0][i] x = index[1][i] position = np.where(self.sudoku[y, x] == 1) if not len(position[0]): return False number = position[0][0] self.sudoku[y, x, number] = 0 self.sudoku[y, :, number] = 0 self.sudoku[:, x, number] = 0 self.sudoku[y - y % 3:y - y % 3 + 3, x - x % 3:x - x % 3 + 3, number] = 0 self.sudoku[y, x, number] = 1 if self.alter_check(origin): self.alter = True else: self.alter = False return True
def depletion_last_number(self): """A while loop to deplete the 'last number'""" result = True self.alter = True while self.alter: self.alter = False result = self.last_number() if not result: break return result
def least_possible_cell(self): """choose a cell with least possible number""" possible_matrix = np.sum(self.sudoku, axis=2) for possibility in range(2, 8): if (possible_matrix == possibility).any(): positions = np.where(possible_matrix == possibility) y = positions[0][0] x = positions[1][0] return y, x
def propose_hypothesis(self, y, x, index): """duplicate a draft and propose the hypothesis""" new_draft = self.duplicate() new_draft.sudoku[y][x] = np.zeros([9, ], dtype='int8') new_draft.sudoku[y][x][index] = 1 return new_draft
def backtrack(self, sudoku): """backtracking method to the exhaustion of any possibilities""" y, x = self.least_possible_cell() hypo_index = np.where(draft.sudoku[y][x] == 1) for index in hypo_index[0]: new_draft = self.propose_hypothesis(y, x, index) new_draft.constrain(sudoku) return
def constrain(self, sudoku): """constrain the possibliity""" result = self.depletion_last_number() if not result: return finish = self.finish_check() if finish: print('find 1 solution.') self.add_solution(sudoku) return self.backtrack(sudoku) return

導入計時器,運行程序

看看能不能成功解決問題

puzzle = '001039000095600000006000780708450900000970305100000000000800020002000400900000500's = Sudoku()s.start_puzzle(puzzle)s.print_sudoku()
draft = Draft()draft.initiation(s)draft.constrain(s)
s.print_answers()

列印結果如下:

秒解,完美,撒花~

最後:

代碼已上傳全球最大同性交友網站GitHub(並不是)

https://github.com/artsuki/Sudoku-Solver

相關焦點

  • 0.7秒解「世界最難數獨」
    前幾天,艾叔輔導小朋友做數獨題時,順便寫了個小程序來解數獨,後來又拿這個程序測試了下號稱「世界最難數獨題」,不到1秒就算出了結果,換做我們人工計算的話
  • Leetcode 37 超越100%算法,解數獨@python
    Leetcode 37 解數獨,具體代碼見上圖,下面講一些注意點1.本題採用算法思想:交集回溯,即使用行、列、3*3矩陣已有數字,通過交集推算每個框可能的數據,然後驗證,驗證不通過回溯,直至驗證通過(1)交集推算每個框可能的數據,大字體數據為已有數據,小數字為可能的解,如下所示:數獨交集圖可能的解
  • 數獨問題的Python求解方案
    ") print("└"+"─"*7 + "┴"+"─"*7 + "┴"+"─"*7 + "┘")printsudoku()解題步驟找到未填充的單元格為了解一個數獨校驗填充數字的有效性我們計劃採用的方法是窮舉法,這樣就必須對每一個填充進來的值進行測試,測試的標準就是依據數獨本身的規則,即每行、每列、每個大方格中數字不重且都不為0:def isValid(sudoku, i, j, e): rowOk = all([e !
  • 馬殿佳出了660道數獨題,「吾皇」「巴扎黑」喊你一起解數獨
    中國青年報客戶端訊(中青報·中青網記者 蔣肖斌)連吾皇、巴扎黑都開始做數獨了,我們還等什麼呢?近日,一套經典數獨題庫《吾皇喊你解數獨》由人民文學出版社出版,國際數獨大賽出題專家馬殿佳擔綱出題人,青年漫畫家白茶繪製插圖。
  • 風靡全球的頭腦體操《吾皇喊你解數獨》來啦!
    數獨遊戲不但需要孩子細心地觀察,還需要手腦並用,數獨書不僅可以讓孩子脫離電子屏幕、提供親子互動內容,最重要的是通過不斷思考、推理、試驗過程,邏輯思維能力能夠有效提升,專注力、數字敏感度也都同時獲得了鍛鍊。
  • 風靡全球的頭腦體操《吾皇喊你解數獨》上市
    打敗「世界數獨第一人」森西亨太的12歲天才少年胡宇軒,愛好做數獨的2017年重慶高考文科狀元劉之銘,2019年世界數獨錦標賽青少年組比賽中包攬冠亞季軍的中國選手明樂天、胡宇軒和黃明睿……從《最強大腦》的天才「小將」到頻刷高分記錄的各省高考狀元,我們似乎進入了一個「神童時代」。
  • Python編寫的超帥數獨可視化解題器
    數獨相信大家都玩過,它被稱為「聰明人的遊戲」,在很多人眼裡:會玩數獨=高智商為什麼?因為數獨能夠培養觀察力,提高反應力:數獨的練習能夠鍛鍊手眼腦的協調性、提高手腦並用的能力,鍛鍊大腦的思維靈活度,全面提高反應力。
  • 風靡全球的頭腦體操《吾皇喊你解數獨》邀你來做題
    近年來,從打敗「世界數獨第一人」森西亨太的12歲天才少年胡宇軒,到愛好做數獨的2017年重慶高考文科狀元劉之銘,再到2019年世界數獨錦標賽青少年組比賽中包攬冠亞季軍的中國選手明樂天、胡宇軒和黃明睿……人們開始關注到數獨,認識到數獨對於培養孩子的邏輯思維能力有促進作用。
  • 數獨,但不僅僅是數獨:八款質量上乘的數獨遊戲
    那些我們平時經常用來評價一款手遊是否優秀的判斷標準,在數獨面前已經全都消失了。 雖然沒法兒用手遊的標準去評價,但數獨畢竟已經登上了移動平臺,成為了手機遊戲中的一部分。那麼,在移動平臺上,什麼樣的數獨遊戲才是好的呢?我自詡是個數獨的資深玩家,在此就來介紹八款質量上乘的數獨遊戲。
  • 你以為給孩子做的數獨都是數獨嗎?
    那屆比賽裡出現了很多冠軍先生認為不是數獨的賽題(我們也認為不是),其中第一種就是非數獨的其他規則謎題。第二種就是多解的題目。但是他們的英文被翻譯到中文還是叫XX數獨了。這一類雖然不是數獨,但是他們對孩子邏輯力的訓練是有效的,只不過如果把他們誤當數獨訓練,可能並不能達到做完這些訓練數獨就能變厲害的效果。二、非唯一解上周別人給我發了一張截圖,是學校老師給學生布置數獨作業,然後家長問老師裡面有些題不是唯一解的,老師解釋說數獨就有不是唯一解的。
  • 外地職校生「秒殺」最難數獨
    芬蘭數學家因卡拉,耗時3個月設計出了世界上迄今難度最大(約為11級)的數獨遊戲,被揚州灣頭鎮一位69歲的老農黃金龍僅花了3天時間就解出。27日、28日,本報對此事進行了連續跟蹤報導。儘管記者連線數獨專業人員,已經證實老漢解法有誤,但仍有不少本地「數友」主動聯繫本報,希望成為老漢的「數友」。昨天,一位廣東學子@本報官方微博「@揚州晚報」,表示已經「秒殺」了這道題。
  • micky給家長們的數獨攻略(內附數獨資源)
    在數獨裡同理,這個空你解不出來,一直盯著它煩惱,久之就會覺得難會退卻。還不如轉移注意力去解其它空,這個轉移的速度就反映了孩子思維的活躍度。也就是大家說的腦子靈不靈活,一根筋行不通哈。時間觀念 關於孩子做事快慢的問題,認知心理學的觀點是,和孩子是否建立時間觀念有關。
  • 數獨輔導:數獨的計算方法
    )數獨,是一種填數字的遊戲,看起來平凡普通、規則簡單,卻因其可訓練嚴謹的邏輯推理能力而風靡全球。根據使用數字的數量不同,數獨可以分為四宮數獨、六宮數獨和九宮數獨等。  解:根據四宮數獨的遊戲規則,每一行、每一列填入的數都是1~4,因此每一行、每一列的「數字和」都是相等的,即1+2+3+4=10。
  • 重慶八旬老翁閉關15天破解世界最難數獨題(圖)
    ■玩數獨5年,陳老著有專著。還記得7月2日重慶晚報刊登的那道數獨難題嗎?這道號稱世界最難的數獨題,吸引了許多高手挑戰。老人名叫陳金康,今年83歲,退休前是重慶師專(現重慶文理學院)數學系主任、副教授,研究數獨已5年,還寫了一本專著。「2007年,我去美國探親,看到當地報紙都刊載有數獨遊戲,試著玩了一下,從此入迷,還買了很多國內外的相關專著來閱讀。」覺得不過癮,陳老又自己寫了一本《速破數獨金鑰匙》,手稿剛完成。7月2日,重慶晚報16版轉載了一道號稱世界最難數獨題,陳老因此動了小試牛刀的念頭。
  • 每天一道數獨題,佳學慧帶你由淺入深學數獨
    相信家長們都會有這樣的經歷,在培訓班或者在網上看到有人宣傳練習數獨的好處,就抱著好奇的心態買了一套數獨盤或者數獨練習冊。但是在連猜帶蒙做了兩道題後就被卡住了。看到練習冊上明明寫的是簡單級別的題,開始懷疑人生。久而久之買來的數獨盤或者練習冊就開始躺在角落裡吃灰了。
  • 重慶一八旬副教授閉關15天破解世界最難數獨題
    老人名叫陳金康,今年83歲,退休前是重慶師專(現重慶文理學院)數學系主任、副教授,研究數獨已5年,還寫了一本專著。「2007年,我去美國探親,看到當地報紙都刊載有數獨遊戲,試著玩了一下,從此入迷,還買了很多國內外的相關專著來閱讀。」覺得不過癮,陳老又自己寫了一本《速破數獨金鑰匙》,手稿剛完成。7月2日,重慶晚報16版轉載了一道號稱世界最難數獨題,陳老因此動了小試牛刀的念頭。
  • 八旬副教授閉關15天 破解世界最難數獨遊戲
    老人名叫陳金康,今年83歲,退休前是重慶師專(現重慶文理學院)數學系主任、副教授,研究數獨已5年,還寫了一本專著。「2007年,我去美國探親,看到當地報紙都刊載有數獨遊戲,試著玩了一下,從此入迷,還買了很多國內外的相關專著來閱讀。」覺得不過癮,陳老又自己寫了一本《速破數獨金鑰匙》,手稿剛完成。7月2日,看見了報紙上一道號稱世界最難數獨題,陳老因此動了小試牛刀的念頭。
  • 數獨練習題1-9級-帶答案!(編號:66)兒童數獨入門、數獨遊戲
    相信家長們都會有這樣的經歷,在培訓班或者在網上看到有人宣傳練習數獨的好處,就抱著好奇的心態買了一套數獨盤或者數獨練習冊。但是在連猜帶蒙做了兩道題後就被卡住了。看到練習冊上明明寫的是簡單級別的題,開始懷疑人生。久而久之買來的數獨盤或者練習冊就開始躺在角落裡吃灰了。    其實解數獨題也是有方法的,單靠猜測是無法體會到數獨真正獨特魅力的。
  • 【數獨技巧連載】——技巧、難度與用時
    本篇主要介紹一下標準數獨難度分類和參考完成時間的不同等級,下面用三道例題作為例子說明: 初級難度題目僅需要排除法就可以全部解完,是數獨愛好者入門時接觸的難度,讓初學者練習運用排除法解題,並熟悉數獨遊戲的觀察模式。
  • 數獨技巧
    基本元素單元格:數獨中最小的單元,標準數獨中共有81個;行:橫向9個單元格的集合;列:縱向9個單元格的集合;宮:粗黑線劃分的區域,標準數獨中為3×3的9個單元格的集合;已知數:數獨初始盤面給出的數字;候選數:每個空單元格中可以填入的數字。