Yet Another Sudoku Solver in Python
2012-08-31 19:56
459 查看
#coding:utf8 import itertools ''' 前面一堆Exception是为了在数独无法进行下去的时候直接跳出来的 Number_Counter 是用来查找唯一数的 Candidate_Counter 是用来查找 N链数的''' class MyException(BaseException): error_message = "" def __init__(self): super(MyException,self).__init__(self.error_message) class DeepException(BaseException): def __init__(self,Max_Recursion_Depth,(i,j,v)): self.error_message = "Error raised when trying to guess (%d, %d) with %d"%(i, j, v) self.error_message += "\n\t\t reach Max_Recursion_Depth which is %d"%Max_Recursion_Depth self.error_message += '\n\t\t change it into a bigger one to avoid this error' super(DeepException,self).__init__() class FillingException(MyException): error_message = "Error raised when trying to fill a number" def __init__(self,(i,j,v)): self.error_message = "Error raised when trying to fill (%d, %d) with %d"%(i, j, v) super(FillingException,self).__init__() class NoNumberSuitEx(MyException): def __init__(self,i,j): self.error_message = "Error raised when trying to guess (%d, %d)"%(i, j) self.error_message += "\n\t\tNot any number suits here" super(NoNumberSuitEx,self).__init__() class WrongSudokuEx(MyException): def __init__(self,i,fun,numbers): self.error_message = "Error raised because this sudoku is wrong" self.error_message += "\n\t\tSee, all numbers in %dth %s are following"%(i,fun.__name__) self.error_message += '\n\t\t%s'%str(numbers) self.error_message += "\n\t\tThis may result from wrong guess before" super(WrongSudokuEx,self).__init__() def disable(fun): '''装饰器,用来取消某些函数的作用 调试的时候可能希望暂时去掉某些搜索方法''' def wrapper(*argv): return set() return wrapper class Number_Counter(dict): def __init__(self): for i in range(1,10): self[str(i)] = [] def extend(self,index,values): if values: for v in values: self[str(v)].append(index) def find_only(self): for key,item in self.items(): if len(item) == 1: return item[0],eval(key) else: return None,None def show(self): for key,item in self.items(): print key,":",items class Candidate_Counter(dict): def __init__(self,List = None): super(Candidate_Counter,self).__init__() if List: self.extend(List) def extend(self, List): if List: for i, j, candidate in List: candidate = list(candidate) candidate.sort() candidate = [str(c) for c in candidate] candidate = ''.join(candidate) if candidate in self: self[candidate].append((i,j)) else: self[candidate] = [(i,j)] def links(self): for key,items in self.items(): if len(key) == len(items) > 1: #print key,items yield items class Index(dict): '''把一些简单的约束封装到类Index里面处理 其中比较重要的罗列如下''' #所有的索引号 all = set((i,j) for i in range(9) for j in range(9)) def row(self,*argv): '''某一行的所有索引号 若输入值是单个值i,则返回第i行所有索引号 若输入值是两个值i,j,则返回(i,j)所在的行的所有索引号''' if len(argv) == 1: i = argv[0] elif len(argv) == 2: i = argv[0] else: raise BaseException("传入row的数据类型错误") return set((i, j) for j in range(9)) def column(self,*argv): if len(argv) == 1: j = argv[0] elif len(argv) == 2: j = argv[1] else: raise BaseException("传入column的数据类型错误") return set((i, j) for i in range(9)) def box(self,*argv): if len(argv) == 2: i, j = argv x, y = i/3*3, j/3*3 elif len(argv) == 1: i = argv[0] x, y = i%3*3, i/3*3 else: raise BaseException("传入box的数据类型错误") return set((x+m, y+n) for m in range(3) for n in range(3)) def candidates_in(self,index_range): assert index_range for (m, n) in index_range & self.unknown: key = hash((m, n)) yield (m, n, self.possible[key]) @property def range_gen(self): return (self.row,self.column,self.box) @property def index_range_gen(self): for i in range(9): for fun in self.range_gen: yield fun(i) def ruled(self,i,j): return (self.row(i) | self.column(j) | self.box(i,j)) - set([(i,j)]) def __init__(self): '''初始化一些成员 self.all 所有索引 (i, j) 并保存在表 self.known = [] 记录所有已知索引的表 self.unknown = self.all 记录所有未知索引的表 self.possible 是一个字典,记录所有未知索引处可以填入的数字 key, item = hash((i, j)), set(range(1,10))''' self.known = set() self.unknown = self.all.copy() self.possible = {} for t in self.unknown: self.possible[hash(t)] = set(range(1, 10)) '''make_known(i, j, value) 填入数据 每次填入的时候,自动维护 known, unknown, 和 possible''' def make_known(self,i,j,value): key = hash((i,j)) assert (i, j ) in self.unknown assert key in self.possible self.unknown.remove((i,j)) self.known.add((i, j, value)) del self.possible[key] #remove value from all relative blanks which are unknown updated = set() for index in self.ruled(i,j) & self.unknown: key = hash(index) if value in self.possible[key]: updated.add(index) self.possible[key].discard(value) return updated def show_possible(self): for i in range(9): for j in range(9): if (i,j) in self.unknown: print str(self.possible[hash((i,j))]).ljust(15), else: print str([]).ljust(15), print "" class Sudoku(list): numbers = set(range(1,10))#shared by all Sudoku def __init__(self,data): '''记录这个类的递归深度''' if not hasattr(data,"level"): self.level = 0 else: self.level = data.level + 1 '''初始化成员 indexs, 它是类 index 的一个对象''' self.indexs = Index() '''从输入参数 data 中初始化自身和 indexs''' for i in range(9): self.append([]) for j in range(9): self[i].append(0) for i, j in self.indexs.all: if data[i][j]: self.setitem(i, j, data[i][j]) def setitem(self,i,j,value): '''调用 setitem 函数填入数据 将自动调用 self.indexs.make_known 维护 self.indexs''' if self[i][j]: raise FillingException((i,j,value)) self[i][j] = value updated = self.indexs.make_known(i,j,value) return updated def find_unique(self,search_range = None): '''生成器: 唯余解法 唯余解法就是某宫格可以添入的数已经排除了8个,那么这个宫格的数字就只能添入那个 4000 没有出现的数字 也就是根据 possible 的长度为 1 来判断该宫格中只能填入 possible 中剩下的数 此外,注意到也有一种解法称作“唯一解法” 比如,当某行已填数字的宫格达到8个,那么该行剩余宫格能填的数字就只剩下那个还没出现过的数字了 因为当某行已填数字的宫格达到8个时,剩余宫格中的 possible 集合中必然最多只能有一个数字 所以本方法涵盖了唯一解法''' if search_range is None: search_range = self.indexs.unknown for i,j in search_range & self.indexs.unknown: key = hash((i, j)) if len(self.indexs.possible[key]) == 1: yield i,j,list(self.indexs.possible[key])[0] def find_only(self, search_range): '''隐性唯一候选数法 根据 search_range 的不同,对行、列或者九宫格进行隐性唯一候选数法搜索 隐性唯一候选数法: 当某个数字在某一列各宫格的候选数中只出现一次时 那么这个数字就是这一列的唯一候选数了 这个宫格的值就可以确定为该数字 这是因为,按照数独游戏的规则要求每一列都应该包含数字1~9 而其它宫格的候选数都不含有该数 则该数不可能出现在其它的宫格,那么就只能出现在这个宫格了 对于唯一候选数出现行,九宫格的情况,处理方法完全相同''' numbers = Number_Counter() for i, j in search_range & self.indexs.unknown: key = hash((i, j)) numbers.extend((i, j), self.indexs.possible[key]) index, value = numbers.find_only() if index: yield index[0], index[1], value def find_only_and_fill(self,search_range = None): '''调用 find_only 查找满足隐性唯一候选数法的宫格并填入数据''' updated = set() if search_range is None: for fun in self.indexs.range_gen: for i in range(9): search_range = fun(i) updated |= self.find_only_and_fill(search_range) else: '''这里才真正调用 find_only ''' for i, j, v in set(self.find_only(search_range)): updated |= self.setitem(i, j, v) if updated: '''如果更新了某些宫格''' self.find_unique_and_fill(search_range & self.indexs.unknown) return updated def find_unique_and_fill(self,search_range = None): updated = set() for i, j ,v in set(self.find_unique(search_range)): updated |= self.setitem(i, j, v) return updated def find_fill(self,search_range): return self.find_unique_and_fill() and self.find_only_and_fill() def chain(self,search_range): updated = set() if not search_range: return updated all_items = self.indexs.candidates_in(search_range) if not all_items: return updated candidate_counter = Candidate_Counter(all_items) for indexs in candidate_counter.links(): for i, j in indexs: key_link = hash((i, j)) values = self.indexs.possible[key_link] for m, n in search_range & self.indexs.unknown - set(indexs): key = hash((m, n)) for v in values: if v in self.indexs.possible[key]: updated.add((m, n)) self.indexs.possible[key].remove(v) if updated: if not self.find_only_and_fill(search_range): self.find_unique_and_fill(search_range) return updated def show(self): for line in self: print line print "" def fill_sure(self): while True: for fun in self.indexs.range_gen: for i in range(9): search_range = fun(i) self.chain(search_range) if not self.find_unique_and_fill() and not self.find_only_and_fill(): break def solved(self): if not self.indexs.unknown and self.check(): setattr(self,"solved",lambda :True) return True else: return False def guesser(self): shortest = () number = 9 index = () for i,j in self.indexs.unknown: key = hash((i,j)) possible = self.indexs.possible[key] if len(possible) < number: index = (i, j) shortest = possible number = len(possible) i, j = index for v in shortest: yield (i,j,v) raise NoNumberSuitEx(i+1, j+1) def solve(self): #self.fill_sure() if self.solved(): return self for i,j,v in self.guesser(): new_puzzle = Sudoku(self) new_puzzle.setitem(i,j,v) try: ans = new_puzzle.solve() return ans except MyException,e: print e continue def check(self): for gen in self.indexs.range_gen: for i in range(9): numbers = set((self[m] for m, n in gen(i))) if numbers != self.numbers: raise WrongSudokuEx(i+1,gen,numbers) return True def stream_to_data(istream): data = [eval(c) for c in istream if c in "0123456789"] data = [data[9*i:9*(i+1)] for i in range(9)] return data def data_to_stream(data): string = [] for line in data: line = [str(n) for n in line] string.append("".join(line)) return "".join(string) if __name__ == "__main__": from time import time counter = 0 #with open("./sudoku.txt","r") as handle: # handle = open("./sudoku.txt",'r') # istream_set = handle.readlines()[0:100] # handle.close() # for istream in istream_set: istream = "450890000000000000008700090607005030090020040040900102070006300000000000000048016" istream = istream.replace(".","0") data = stream_to_data(istream) START = time() istream = istream.replace("\r\n","").replace(" ","") #print len(istream) print 'Q:\t',istream s = Sudoku(data) y = s.solve() if "0" not in str(y): print "Succeed, time used: ", (time() - START)*1e3, "ms :",y.check() pass else: print "%d Fail , time used: "%counter, (time() - START)*1e3, "ms:" print 'A:\t',data_to_stream(y) print "" ''' Q: 450890000000000000008700090607005030090020040040900102070006300000000000000048016 Succeed, time used: 12.7029418945 ms : True A: 452891673769534821318762495627415938891623547543987162274156389186379254935248716 '''
相关文章推荐
- C/C++嵌入Python(Embedding Python in Another Application)
- [leetcode]Sudoku Solver @ Python
- Firefox无法启动,提示Profile is yet in use by another Firefox
- 37. Sudoku Solver leetcode python new season 2016
- GYM 100488 A. Yet Another Goat in the Garden(计算几何)
- leetcode Sudoku Solver python
- Python unittesting: run tests in another module
- 37. Sudoku Solver Leetcode Python
- [leetcode]Sudoku Solver @ Python
- Embedding Python in Another Application
- yet another blog: first blog in CSDN @ July 21, 2013 palo alto
- comment:Yet Another Generalized Functors Implementation in C++
- . Embedding Python in Another Application¶
- Profile is yet in use by another thunderbird
- danger JLabel.settext() in another thread
- python inconsistent use of tabs and spaces in indentation
- Python 报错:SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3:
- befriending a template in another namespace
- python中in在list和dict中查找效率比较
- 高精度 I - Yet another A + B