import itertools from itertools import combinations, combinations_with_replacement OP_PLUS = "PLUS" OP_MINUS = "MINUS" OP_MULTIPLY = "MULTIPLY" OP_DIVIDE = "DIVIDE" OP_NONE = "NONE" def lambda_mul(x): rv = 1 for v in x: rv *= v return rv OP_LAMBDAS = { OP_PLUS: sum, OP_MINUS: lambda x: abs(x[0] - x[1]), OP_MULTIPLY: lambda_mul, OP_DIVIDE: lambda x: x[0] / x[1] if x[0] % x[1] == 0 else x[1] / x[0], OP_NONE: lambda x: x[0], } BOX_H_LABELS = 'ABCDEF' BOX_V_LABELS = '123456' class Block(object): def __init__(self, boxes, operation, result): self.boxes = boxes self.operation = operation self.result = result self.solutions = [] self.verify() def verify(self): assert self.operation in OP_LAMBDAS if self.operation in [OP_NONE]: assert len(self.boxes) == 1 elif self.operation in [OP_MINUS, OP_DIVIDE]: assert len(self.boxes) == 2 else: assert len(self.boxes) > 1 for box in self.boxes: assert len(box) == 2 assert box[0] in BOX_H_LABELS assert box[1] in BOX_V_LABELS def all_boxes_in_one_row_or_column(self): is_same_column, is_same_row = True, True for box in self.boxes[1:]: if self.boxes[0][0] != box[0]: is_same_column = False if self.boxes[0][1] != box[1]: is_same_row = False return is_same_row or is_same_column def generate_combinations(self, k, n): assert 1 <= k < n # Exploit the structure of the problem # For block of size 2, we don't need comb with replacement as they will # neccesarily be in different columns/rows if k == 2: return list(combinations(range(1, n + 1), k)) # for 3 and more we don't need if all of the boxes are on the # same row or same column elif k > 2: if self.all_boxes_in_one_row_or_column(): return list(combinations(range(1, n + 1), k)) return list(combinations_with_replacement(range(1, n + 1), k)) def generate_hypotheses(self, grid_size): rv = [] op = OP_LAMBDAS[self.operation] hypotheses = self.generate_combinations(k=len(self.boxes), n=grid_size) for hypothesis in hypotheses: if op(hypothesis) == self.result: rv.append(hypothesis) return rv def generate_solutions(self, grid_size): self.generate_hypotheses(grid_size) for hyp in self.generate_hypotheses(grid_size): for perm in itertools.permutations(hyp): sol = list(zip(self.boxes, perm)) self.solutions.append(sol) def __repr__(self): return 'Block {}'.format(self.boxes) class Game(object): def __init__(self, grid_size, blocks): self.blocks = blocks self.grid_size = grid_size self.grid = [[0] * grid_size] * grid_size self.verify() def verify(self): found = set() for block in self.blocks: for box in block.boxes: assert box not in found found.add(box) assert len(found) == self.grid_size ** 2 def find_blocks_in_row(self, row_num): rv = set() for block in self.blocks: for box in block.boxes: if int(box[1]) == row_num: rv.add(block) return rv def solve(self): for block in self.blocks: block.generate_solutions(self.grid_size) # row_permutations = itertools.permutations(1, self.grid_size + 1) for row_id, row in enumerate(self.grid): for col_id, cell in enumerate(row): self.grid[row_id][col_id] = self.generate_possible_cell_values(row_id, col_id) def main(): game = Game(grid_size=6, blocks=[ Block(boxes=['A6', 'A5'], operation=OP_MINUS, result=2), Block(boxes=['B6', 'C6', 'C5'], operation=OP_MULTIPLY, result=20), Block(boxes=['D6', 'D5', 'E5'], operation=OP_PLUS, result=11), Block(boxes=['E6', 'F6', 'F5'], operation=OP_MULTIPLY, result=18), Block(boxes=['A4', 'B4', 'B5'], operation=OP_PLUS, result=9), Block(boxes=['C4', 'D4'], operation=OP_MINUS, result=5), Block(boxes=['E4', 'F4', 'E3', 'F3'], operation=OP_MULTIPLY, result=80), Block(boxes=['D3', 'C3'], operation=OP_MINUS, result=1), Block(boxes=['A3', 'B3', 'B2'], operation=OP_PLUS, result=12), Block(boxes=['C2', 'D2'], operation=OP_DIVIDE, result=2), Block(boxes=['A1', 'A2'], operation=OP_PLUS, result=5), Block(boxes=['B1'], operation=OP_NONE, result=6), Block(boxes=['C1', 'D1', 'E1'], operation=OP_MULTIPLY, result=60), Block(boxes=['F1', 'E2', 'F2'], operation=OP_MULTIPLY, result=9), ]) game.solve() if __name__ == '__main__': main()