sudoku.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. assignments = []
  2. class Sudoku():
  3. """Initializing and solving a classic/diagnol Sudoku.
  4. ATTRIBUTES:
  5. grid : row-wise string representation of Sudoku
  6. row : string 'ABCDEFGHI'
  7. cols : string '123456789'
  8. boxes : list of all squares
  9. unit_list : list of all units
  10. peers : dictionary of peers for each box
  11. values : dictionary of box:digits
  12. """
  13. def __init__(self, grid, partial=False, diag=False,rows='ABCDEFGHI', cols='123456789'):
  14. """
  15. Args:
  16. - grid : 81-char string to be solved if 'partial = False'
  17. else intermediate solution as a dict of box:values
  18. - partial : init with string or partial solution
  19. - diag : boolean - diagonal Sudoku = True
  20. Returns:
  21. All class attributes initialized
  22. """
  23. self.grid = grid
  24. self.rows = rows
  25. self.cols = cols
  26. self.grid_init(partial, diag)
  27. def cross(self, A, B):
  28. """
  29. Cross product of elements in a and b
  30. """
  31. return [a+b for a in A for b in B]
  32. def assign_value(self, box, value):
  33. """Update the values dictionary.
  34. Assigns a value to a given box. If it updates the board, records it.
  35. """
  36. self.values[box] = value
  37. if len(value) == 1:
  38. assignments.append(self.values.copy())
  39. def grid_init(self, partial, diag):
  40. """Setup the grid and initialize difference parameters.
  41. i.e. Boxes, Values, Peers.
  42. """
  43. self.boxes = self.cross(self.rows,self.cols)
  44. if partial:
  45. self.values = self.grid.copy()
  46. else:
  47. self.values = {}
  48. for box,char in zip(self.boxes,self.grid):
  49. if char == '.':
  50. self.values[box] = self.cols
  51. else:
  52. self.values[box] = char
  53. rowunits = [self.cross(r,self.cols) for r in self.rows]
  54. colunits = [self.cross(self.rows,c) for c in self.cols]
  55. squareunits = [self.cross(rs, cs) for rs in ['ABC','DEF','GHI'] for cs in ['123','456','789']]
  56. self.unitlist = rowunits + colunits + squareunits
  57. if diag:
  58. self.unitlist += [[r+c for r,c in zip(self.rows,self.cols)],
  59. [r+c for r,c in zip(self.rows,self.cols[::-1])]]
  60. units = {box:[u for u in self.unitlist if box in u] for box in self.boxes}
  61. self.peers = {box:set(sum(units[box],[]))-set([box]) for box in self.boxes}
  62. def display(self):
  63. """Display the values as a 2-D grid.
  64. Args:
  65. values(dict): The sudoku in dictionary form
  66. """
  67. width = 1+max(len(self.values[s]) for s in self.boxes)
  68. line = '+'.join(['-'*(width*3)]*3)
  69. for r in self.rows:
  70. print(''.join(self.values[r+c].center(width)+('|' if c in '36' else '')
  71. for c in self.cols))
  72. if r in 'CF': print(line)
  73. print('\n')
  74. def solved_values(self):
  75. return [box for box in self.boxes if len(self.values[box]) == 1]
  76. def eliminate(self):
  77. """Eliminate values from peers of each box with a single value.
  78. Go through all the boxes, and whenever there is a box with a single
  79. value, eliminate this value from the set of values of all its peers.
  80. Args:
  81. values: Sudoku in dictionary form
  82. Returns:
  83. Resulting Sudoku in dictionary form after eliminating values
  84. """
  85. solved_values_boxes = self.solved_values()
  86. for box in solved_values_boxes:
  87. digit = self.values[box]
  88. for peer in self.peers[box]:
  89. value = self.values[peer]
  90. value = value.replace(digit,'')
  91. self.assign_value(peer, value)
  92. def only_choice(self):
  93. """Finalize all values that are the only choice for a unit.
  94. Go through all the units, and whenever there is a unit with a value
  95. that only fits in one box, assign the value to this box.
  96. Input: Sudoku in dictionary form.
  97. Output: Resulting Sudoku in dictionary form after filling in only
  98. choices.
  99. """
  100. for unit in self.unitlist:
  101. for digit in self.cols:
  102. boxes_containing_digit =[box for box in unit if digit in self.values[box]]
  103. if len(boxes_containing_digit) == 1:
  104. self.assign_value(boxes_containing_digit[0], digit)
  105. #Naked-Twins Stragtegy
  106. def find_naked_twins(self):
  107. twins = [[a for a in u
  108. for b in u
  109. if (a != b) and (len(self.values[a]) == 2)
  110. and (self.values[a] == self.values[b])
  111. ]
  112. for i,u in enumerate(self.unitlist)
  113. ]
  114. return twins
  115. def eliminate_naked_twins(self, twins):
  116. for i, twin in enumerate(twins):
  117. if twin:
  118. for box in self.unitlist[i]:
  119. if box not in twin:
  120. for digit in self.values[twin[0]]:
  121. value = self.values[box]
  122. if len(value) > 1 :
  123. value = value.replace(digit,'')
  124. self.assign_value(box, value)
  125. def naked_twins(self):
  126. """Eliminate values using the naked twins strategy.
  127. Args:
  128. values(dict): a dictionary of the form {'box_name': '123456789', ...}
  129. Returns:
  130. the values dictionary with the naked twins eliminated from peers.
  131. """
  132. #Find all instances of naked twins
  133. twins = self.find_naked_twins()
  134. #Eliminate the naked twins as possibilities for their units
  135. self.eliminate_naked_twins(twins)
  136. def reduce_puzzle(self):
  137. """Iterate eliminate() and only_choice().
  138. If at some point, there is a box with no available values, return False.
  139. If the sudoku is solved, return the sudoku.
  140. If after an iteration of both functions, the sudoku remains the same,
  141. return the sudoku.
  142. Input: A sudoku in dictionary form.
  143. Output: The resulting sudoku in dictionary form.
  144. """
  145. stalled = False
  146. while not stalled:
  147. solved_values_before = self.solved_values()
  148. self.eliminate()
  149. self.only_choice()
  150. solved_values_after = self.solved_values()
  151. stalled = solved_values_before == solved_values_after
  152. if len([box for box in self.boxes if len( self.values[box]) == 0]):
  153. # if it is still solvable run naked_twins or not
  154. self.naked_twins()
  155. return True
  156. return False
  157. def search(self):
  158. """Using depth-first search and propagation,
  159. create a search tree and solve the sudoku.
  160. """
  161. # Reduce the puzzle
  162. failed = self.reduce_puzzle()
  163. if failed :
  164. return False #Tree-leaf: not a solution
  165. if all(len(self.values[box]) == 1 for box in self.boxes):
  166. return True #Solved
  167. # Choose box with min possibilities
  168. n, box = min((len(self.values[box]),box) for box in self.boxes if len(self.values[box]) > 1)
  169. # Recursion to solve each one of the resulting sudokus,
  170. # and if one returns a value (not False), return that answer!
  171. for digit in self.values[box]:
  172. tmp = self.values.copy() # copy of the values in case of failure = 'attempt ===False'
  173. self.values[box] = digit
  174. attempt = self.search()
  175. if attempt:
  176. return attempt
  177. else:
  178. self.values = tmp