Initial commit
This commit is contained in:
commit
54b6f58333
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
venv
|
||||
.idea
|
||||
*.pyc
|
||||
__pycache__
|
150
main.py
Normal file
150
main.py
Normal file
@ -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()
|
Loading…
Reference in New Issue
Block a user