|
| 1 | +class Mul(): |
| 2 | + def combine(self, A, B): |
| 3 | + "Combines the elements of two given arrays." |
| 4 | + return [a + b for a in A for b in B] |
| 5 | + |
| 6 | + |
| 7 | +class SudokuSolver(Mul): |
| 8 | + |
| 9 | + def __init__(self, grid=''): |
| 10 | + self.numbers = '123456789' |
| 11 | + self.characters = 'ABCDEFGHI' |
| 12 | + self.cols = self.numbers |
| 13 | + comb = Mul() |
| 14 | + self.squares = comb.combine(self.characters, self.cols) |
| 15 | + |
| 16 | + self.Total = ([comb.combine(self.characters, c) for c in self.cols] + |
| 17 | + [comb.combine(r, self.cols) for r in self.characters] + |
| 18 | + [comb.combine(rs, cs) for rs in ('ABC', 'DEF', 'GHI') for cs in ('123', '456', '789')]) |
| 19 | + |
| 20 | + self.belongs = dict((s, [u for u in self.Total if s in u]) |
| 21 | + for s in self.squares) |
| 22 | + |
| 23 | + self.peers = dict((s, set(sum(self.belongs[s], [])) - set([s])) |
| 24 | + for s in self.squares) |
| 25 | + |
| 26 | + def possible_values(self, grid): |
| 27 | + "Lists all the possible values for a particular cell." |
| 28 | + |
| 29 | + values = dict((s, self.numbers) for s in self.squares) |
| 30 | + for s, d in self.StrToDict(grid).items(): |
| 31 | + if d in self.numbers and not self.update(values, s, d): |
| 32 | + return False |
| 33 | + return values |
| 34 | + |
| 35 | + def StrToDict(self, grid): |
| 36 | + "Converts the given string of sudoku values into dictionary." |
| 37 | + values = [c for c in grid if c in self.numbers or c in '0.'] |
| 38 | + assert len(values) == 81 |
| 39 | + return dict(zip(self.squares, values)) |
| 40 | + |
| 41 | + def update(self, values, s, d): |
| 42 | + "Keeps updating the values by elimination of other values." |
| 43 | + other_values = values[s].replace(d, '') |
| 44 | + if all(self.remove(values, s, d2) for d2 in other_values): |
| 45 | + return values |
| 46 | + else: |
| 47 | + return False |
| 48 | + |
| 49 | + def remove(self, values, s, d): |
| 50 | + """Eliminate d from values[s]; propagate when values or places <= 2. |
| 51 | + Return values, except return False if a contradiction is detected.""" |
| 52 | + if d not in values[s]: |
| 53 | + return values |
| 54 | + values[s] = values[s].replace(d, '') |
| 55 | + |
| 56 | + if len(values[s]) == 0: |
| 57 | + return False |
| 58 | + elif len(values[s]) == 1: |
| 59 | + d2 = values[s] |
| 60 | + if not all(self.remove(values, s2, d2) for s2 in self.peers[s]): |
| 61 | + return False |
| 62 | + |
| 63 | + for u in self.belongs[s]: |
| 64 | + dplaces = [s for s in u if d in values[s]] |
| 65 | + if len(dplaces) == 0: |
| 66 | + return False |
| 67 | + elif len(dplaces) == 1: |
| 68 | + |
| 69 | + if not self.update(values, dplaces[0], d): |
| 70 | + return False |
| 71 | + return values |
| 72 | + |
| 73 | + def solve(self, grid): |
| 74 | + return self.depth_first(self.possible_values(grid)) |
| 75 | + |
| 76 | + def depth_first(self, values): |
| 77 | + "Using depth-first search and propagation, try all possible values." |
| 78 | + if values is False: |
| 79 | + return False |
| 80 | + |
| 81 | + if all(len(values[s]) == 1 for s in self.squares): |
| 82 | + return values |
| 83 | + |
| 84 | + n, s = min((len(values[s]), s) for s in self.squares if len(values[s]) > 1) |
| 85 | + return self.selected(self.depth_first(self.update(values.copy(), s, d)) |
| 86 | + for d in values[s]) |
| 87 | + |
| 88 | + def selected(self, seq): |
| 89 | + "Return some element of seq that is true." |
| 90 | + for e in seq: |
| 91 | + if e: return e |
| 92 | + return False |
| 93 | + |
| 94 | + |
0 commit comments