commit 54b6f5833305e384227402553a4dea8861037d2f Author: Daniel Tsvetkov Date: Mon Apr 4 20:41:30 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..874e112 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +venv +.idea +*.pyc +__pycache__ diff --git a/main.py b/main.py new file mode 100644 index 0000000..78ef184 --- /dev/null +++ b/main.py @@ -0,0 +1,150 @@ +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()