123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- assignments = []
- class Sudoku():
- """Initializing and solving a classic/diagnol Sudoku.
- ATTRIBUTES:
- grid : row-wise string representation of Sudoku
- row : string 'ABCDEFGHI'
- cols : string '123456789'
- boxes : list of all squares
- unit_list : list of all units
- peers : dictionary of peers for each box
- values : dictionary of box:digits
- """
- def __init__(self, grid, partial=False, diag=False,rows='ABCDEFGHI', cols='123456789'):
- """
- Args:
- - grid : 81-char string to be solved if 'partial = False'
- else intermediate solution as a dict of box:values
- - partial : init with string or partial solution
- - diag : boolean - diagonal Sudoku = True
- Returns:
- All class attributes initialized
- """
- self.grid = grid
- self.rows = rows
- self.cols = cols
- self.grid_init(partial, diag)
- def cross(self, A, B):
- """
- Cross product of elements in a and b
- """
- return [a+b for a in A for b in B]
- def assign_value(self, box, value):
- """Update the values dictionary.
- Assigns a value to a given box. If it updates the board, records it.
- """
- self.values[box] = value
- if len(value) == 1:
- assignments.append(self.values.copy())
- def grid_init(self, partial, diag):
- """Setup the grid and initialize difference parameters.
- i.e. Boxes, Values, Peers.
- """
- self.boxes = self.cross(self.rows,self.cols)
- if partial:
- self.values = self.grid.copy()
- else:
- self.values = {}
- for box,char in zip(self.boxes,self.grid):
- if char == '.':
- self.values[box] = self.cols
- else:
- self.values[box] = char
- rowunits = [self.cross(r,self.cols) for r in self.rows]
- colunits = [self.cross(self.rows,c) for c in self.cols]
- squareunits = [self.cross(rs, cs) for rs in ['ABC','DEF','GHI'] for cs in ['123','456','789']]
- self.unitlist = rowunits + colunits + squareunits
- if diag:
- self.unitlist += [[r+c for r,c in zip(self.rows,self.cols)],
- [r+c for r,c in zip(self.rows,self.cols[::-1])]]
- units = {box:[u for u in self.unitlist if box in u] for box in self.boxes}
- self.peers = {box:set(sum(units[box],[]))-set([box]) for box in self.boxes}
- def display(self):
- """Display the values as a 2-D grid.
- Args:
- values(dict): The sudoku in dictionary form
- """
- width = 1+max(len(self.values[s]) for s in self.boxes)
- line = '+'.join(['-'*(width*3)]*3)
- for r in self.rows:
- print(''.join(self.values[r+c].center(width)+('|' if c in '36' else '')
- for c in self.cols))
- if r in 'CF': print(line)
- print('\n')
- def solved_values(self):
- return [box for box in self.boxes if len(self.values[box]) == 1]
- def eliminate(self):
- """Eliminate values from peers of each box with a single value.
- Go through all the boxes, and whenever there is a box with a single
- value, eliminate this value from the set of values of all its peers.
- Args:
- values: Sudoku in dictionary form
- Returns:
- Resulting Sudoku in dictionary form after eliminating values
- """
- solved_values_boxes = self.solved_values()
- for box in solved_values_boxes:
- digit = self.values[box]
- for peer in self.peers[box]:
- value = self.values[peer]
- value = value.replace(digit,'')
- self.assign_value(peer, value)
- def only_choice(self):
- """Finalize all values that are the only choice for a unit.
- Go through all the units, and whenever there is a unit with a value
- that only fits in one box, assign the value to this box.
- Input: Sudoku in dictionary form.
- Output: Resulting Sudoku in dictionary form after filling in only
- choices.
- """
- for unit in self.unitlist:
- for digit in self.cols:
- boxes_containing_digit =[box for box in unit if digit in self.values[box]]
- if len(boxes_containing_digit) == 1:
- self.assign_value(boxes_containing_digit[0], digit)
- #Naked-Twins Stragtegy
- def find_naked_twins(self):
- twins = [[a for a in u
- for b in u
- if (a != b) and (len(self.values[a]) == 2)
- and (self.values[a] == self.values[b])
- ]
- for i,u in enumerate(self.unitlist)
- ]
- return twins
- def eliminate_naked_twins(self, twins):
- for i, twin in enumerate(twins):
- if twin:
- for box in self.unitlist[i]:
- if box not in twin:
- for digit in self.values[twin[0]]:
- value = self.values[box]
- if len(value) > 1 :
- value = value.replace(digit,'')
- self.assign_value(box, value)
- def naked_twins(self):
- """Eliminate values using the naked twins strategy.
- Args:
- values(dict): a dictionary of the form {'box_name': '123456789', ...}
- Returns:
- the values dictionary with the naked twins eliminated from peers.
- """
- #Find all instances of naked twins
- twins = self.find_naked_twins()
- #Eliminate the naked twins as possibilities for their units
- self.eliminate_naked_twins(twins)
- def reduce_puzzle(self):
- """Iterate eliminate() and only_choice().
- If at some point, there is a box with no available values, return False.
- If the sudoku is solved, return the sudoku.
- If after an iteration of both functions, the sudoku remains the same,
- return the sudoku.
- Input: A sudoku in dictionary form.
- Output: The resulting sudoku in dictionary form.
- """
- stalled = False
- while not stalled:
- solved_values_before = self.solved_values()
- self.eliminate()
- self.only_choice()
- solved_values_after = self.solved_values()
- stalled = solved_values_before == solved_values_after
- if len([box for box in self.boxes if len( self.values[box]) == 0]):
- # if it is still solvable run naked_twins or not
- self.naked_twins()
- return True
- return False
- def search(self):
- """Using depth-first search and propagation,
- create a search tree and solve the sudoku.
- """
- # Reduce the puzzle
- failed = self.reduce_puzzle()
- if failed :
- return False #Tree-leaf: not a solution
- if all(len(self.values[box]) == 1 for box in self.boxes):
- return True #Solved
- # Choose box with min possibilities
- n, box = min((len(self.values[box]),box) for box in self.boxes if len(self.values[box]) > 1)
- # Recursion to solve each one of the resulting sudokus,
- # and if one returns a value (not False), return that answer!
- for digit in self.values[box]:
- tmp = self.values.copy() # copy of the values in case of failure = 'attempt ===False'
- self.values[box] = digit
- attempt = self.search()
- if attempt:
- return attempt
- else:
- self.values = tmp
|