前幾天和隔壁鄰居玩鬥地主被發現了,牌被沒收了,鬥地主是鬥不了了,但我還想和鄰居玩耍。若是你還想鬥鬥地主,戳:趁老王不在,和隔壁鄰居鬥鬥地主,比比大小python
想破腦殼終於讓我想到一個遊戲,數獨!什麼叫數獨?數獨就是可讓我趁老王不在的時候和隔壁鄰居一塊兒玩耍的遊戲!git
一、數字 1-9 在每一行只能出現一次。github
二、數字 1-9 在每一列只能出現一次。windows
三、數字 1-9 在每個 3x3 宮內只能出現一次。3x3 的宮內爲A1-C3,A4-C6,A7-C9,D1-F3,D4-F6,D7-F9...數組
一、數獨咱們使用一個二維列表存儲,沒有值的位置咱們使用''空字符竄佔位。(二維數組)app
二、獲得每個3*3的宮內,每一行,每一列已有的數據,而後存放起來。三、獲得全部的空缺位置,再遍歷空缺位置,嘗試放置數據,而後進行判斷,若是知足條件安繼續放置下一個。以此類推,在途中有不知足條件的狀況,就進行回溯,返回上一次知足條件的狀況,在進行另外一次嘗試。編輯器
一、首選咱們建立一個類SudoKu
。編寫構造函數。函數
class SudoKu(): def __init__(self,sudo_ku_data): # 判斷傳入的數獨是否知足格式 if not isinstance(sudo_ku_data,list): raise TypeError(f'sudo_ku_data params must a list, but {sudo_ku_data} is a {type(sudo_ku_data)}') if len(sudo_ku_data) != 9 or len(sudo_ku_data[0]) != 9: raise TypeError(f'sudo_ku_data params must a 9*9 list, but {sudo_ku_data} is a {len(sudo_ku_data)}*{len(sudo_ku_data[0])} list') self.sudo_ku = sudo_ku_data # 存放每一行已有的數據 self.every_row_data = {} # 每一列已有的數字 self.every_column_data = {} # 每個3*3宮內有的數字 self.every_three_to_three_data = {} # 每個空缺的位置 self.vacant_position = [] # 每個空缺位置嘗試了的數字 self.every_vacant_position_tried_values = {}
二、編寫添加每一行,每一列,每一宮方法,方便咱們後面調用測試
def _add_row_data(self,row,value): ''' 添加數據到self.every_row_data中,即對每一行已有的數據進行添加 :param row: :param value: :return: ''' # 若是當前行不存在,就以當前行爲key,初始化值爲set()(空的集合) if row not in self.every_row_data: self.every_row_data[row] = set() # 若是這個值已經出現過在這一行了,說明傳入的不是一個正確的數獨 if value in self.every_row_data[row]: raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu') self.every_row_data[row].add(value) def _add_column_data(self,column,value): ''' 添加數據到self.every_column_data中,上面的函數思路同樣 :param column: :param value: :return: ''' if column not in self.every_column_data: self.every_column_data[column] = set() if value in self.every_column_data[column]: raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu') self.every_column_data[column].add(value) def _get_three_to_three_key(self,row,column): ''' 獲得該位置在哪個3*3的宮內 :param row: :param column: :return: ''' if row in [0,1,2]: if column in [0,1,2]: key = 1 elif column in [3,4,5]: key = 2 else: key = 3 elif row in [3,4,5]: if column in [0,1,2]: key = 4 elif column in [3,4,5]: key = 5 else: key = 6 else: if column in [0,1,2]: key = 7 elif column in [3,4,5]: key = 8 else: key = 9 return key def _add_three_to_three_data(self,row,column,value): ''' 添加數據到self.every_three_to_three_data中 :param row: :param column: :param value: :return: ''' # 首先獲得在哪個3*3的宮內 key = self._get_three_to_three_key(row,column) # 而後也和上面添加行,列的思路同樣 if key not in self.every_three_to_three_data: self.every_three_to_three_data[key] = set() if value in self.every_three_to_three_data[key]: raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu') self.every_three_to_three_data[key].add(value)
三、遍歷數獨,對每種數據進行初始化操作系統
def _init(self): ''' 根據傳入的數獨,初始化數據 :return: ''' for row,row_datas in enumerate(self.sudo_ku): for column,value in enumerate(row_datas): if value == '': # 添加空缺位置 self.vacant_position.append( (row,column) ) else: # 添加行數據 self._add_row_data(row,value) # 添加列數據 self._add_column_data(column,value) # 添加宮數據 self._add_three_to_three_data(row,column,value)
四、編寫判斷某一個位置的值是否合法的函數
def _judge_value_is_legal(self,row,column,value): ''' 判斷方放置的數據是否合法 :param row: :param column: :param value: :return: ''' # value是否存在這一行數據中 if value in self.every_row_data[row]: return False # value是否存在這一列數據中 if value in self.every_column_data[column]: return False # value是否存在這個3*3的宮內 key = self._get_three_to_three_key(row,column) if value in self.every_three_to_three_data[key]: return False return True
五、編寫計算的函數,在當前位置循環 可使用的額數據,肯定能夠是否能夠放置這個值
def _calculate(self, vacant_position): ''' 計算,開始對數獨進行放置值 :param vacant_position: :return: ''' # 獲得當前位置 row,column = vacant_position values = set(range(1,10)) # 對當前爲位置建立一個惟一key,用來存放當前位置已經嘗試了的數據 key = str(row) + str(column) # 若是這個key存在,就對values進行取差集,由於兩個都是集合(set),直接使用-就好了 if key in self.every_vacant_position_tried_values: values = values - self.every_vacant_position_tried_values[key] # 若是這個key不存在,就建立一個空的集合 else: self.every_vacant_position_tried_values[key] = set() for value in values: # 對當前數據添加到當前位置嘗試過的的數據中 self.every_vacant_position_tried_values[key].add(value) # 若是當前value合法,能夠放置 if self._judge_value_is_legal(row,column,value): print(f'set {vacant_position} value is {value}') # 更新 判斷數據合法時 須要使用到的數據 self.every_column_data[column].add(value) self.every_row_data[row].add(value) key = self._get_three_to_three_key(row,column) self.every_three_to_three_data[key].add(value) # 修改這個位置的值爲value self.sudo_ku[row][column] = value # 返回True 和填充的 value return True,value return False,None
六、若是當前位置沒有任何一個值能夠放置,那麼就回溯,返回上一次成功的位置,從新取值,因此咱們編寫一個回溯函數
def _backtrack(self,current_vacant_position,previous_vacant_position,previous_value): ''' 回溯 :param current_vacant_position: 當前嘗試失敗的位置 :param previous_vacant_position: 上一次成功的位置 :param previous_value:上一次成功的值 :return: ''' print(f"run backtracking... value is {previous_value},vacant position is {previous_vacant_position}") row,column = previous_vacant_position # 對上一次成功的值從須要用到的判斷的數據中移除 self.every_column_data[column].remove(previous_value) self.every_row_data[row].remove(previous_value) key = self._get_three_to_three_key(row,column) self.every_three_to_three_data[key].remove(previous_value) # 而且上一次改變的的值變回去 self.sudo_ku[row][column] = '' # 對當前嘗試失敗的位置已經城市失敗的的值進行刪除,由於回溯了,因此下一次進來須要從新判斷值 current_row,current_column = current_vacant_position key = str(current_row) + str(current_column) self.every_vacant_position_tried_values.pop(key)
七、到這裏爲止,咱們全部的功能函數都寫完了,而後咱們編寫一個函數,開始循環全部的空缺位置。而後進行計算。
def get_result(self): ''' 獲得計算以後的數獨 :return: ''' # 首先初始化一下數據 self._init() # 空缺位置的長度 length = len(self.vacant_position) # 空缺位置的下標 index = 0 # 存放已經嘗試了的數據 tried_values = [] # 若是index小於length,說明尚未計算完 while index < length: # 獲得一個空缺位置 vacant_position = self.vacant_position[index] # 計入計算函數,返回是否成功,若是成功,value爲成功 的值,若是失敗,value爲None is_success,value = self._calculate(vacant_position) # 若是成功,將value放在tried_values列表裏面,由於列表是有序的. # index+1 對下一個位置進行嘗試 if is_success: tried_values.append(value) index += 1 # 失敗,進行回溯,而且index-1,返回上一次的空缺位置,咱們須要傳入當前失敗的位置 和 上一次成功的位置和值 else: self._backtrack(vacant_position,self.vacant_position[index-1],tried_values.pop()) index -= 1 # 若是index<0 了 說明這個數獨是無效的 if index < 0: raise ValueError(f'{self.sudo_ku} is a invalid sudo ku') # 返回計算以後的數獨 return self.sudo_ku
呼。。。終於幹完代碼,接下來咱們呢能夠"開始收穫"了
if __name__ == '__main__': sudo_ku_data = [ [5,3,'','',7,'','','',''], [6,'','',1,9,5,'','',''], ['',9,8,'','','','',6,''], [8,'','','',6,'','','',3], [4,'','',8,'',3,'','',1], [7,'','','',2,'','','',6], ['',6,'','','','',2,8,''], ['','','',4,1,9,'','',5], ['','','','',8,'','',7,9], ] # 獲得計算好的數獨 sudo_ku = SudoKu(sudo_ku_data).get_result() print(sudo_ku) ################ # 結果顯示 # ################ [5, 3, 4, 6, 7, 8, 9, 1, 2] [6, 7, 2, 1, 9, 5, 3, 4, 8] [1, 9, 8, 3, 4, 2, 5, 6, 7] [8, 5, 9, 7, 6, 1, 4, 2, 3] [4, 2, 6, 8, 5, 3, 7, 9, 1] [7, 1, 3, 9, 2, 4, 8, 5, 6] [9, 6, 1, 5, 3, 7, 2, 8, 4] [2, 8, 7, 4, 1, 9, 6, 3, 5] [3, 4, 5, 2, 8, 6, 1, 7, 9]
這效果就很完美啊,咱們在來測試一個比較可貴數獨。
輸入數獨爲:
[ [8, '', '', '', '', '', '', '', 4], ['', 2, '', '', '', '', '', 7, ''], ['', '', 9, 1, '', 6, 5, '', ''], ['', '', 6, 2, '', 8, 9, '', ''], ['', 9, '', '', 3, '', '', 4, ''], ['', '', 2, 4, '', 7, 8, '', ''], ['', '', 7, 9, '', 5, 6, '', ''], ['', 8, '', '', '', '', '', 2, ''], [6, '', '', '', '', '', '', '', 9], ] ################ # 結果顯示 # ################ [8, 6, 1, 5, 7, 2, 3, 9, 4] [5, 2, 4, 3, 8, 9, 1, 7, 6] [3, 7, 9, 1, 4, 6, 5, 8, 2] [4, 3, 6, 2, 5, 8, 9, 1, 7] [7, 9, 8, 6, 3, 1, 2, 4, 5] [1, 5, 2, 4, 9, 7, 8, 6, 3] [2, 4, 7, 9, 1, 5, 6, 3, 8] [9, 8, 5, 7, 6, 3, 4, 2, 1] [6, 1, 3, 8, 2, 4, 7, 5, 9]
哈哈哈哈哈,之後還有誰可以和我比解數獨。膨脹.jpg
代碼已所有上傳至Github:https://github.com/MiracleYoung/You-are-Pythonista/tree/master/PythonExercise/App/solveSudoku/xujin
更多好玩有趣的Python盡請關注「Python專欄」