added comments
This commit is contained in:
parent
2264ff4663
commit
9997434ac2
189
main.py
189
main.py
@ -28,72 +28,6 @@ BOX_H_LABELS = 'ABCDEF'
|
||||
BOX_V_LABELS = '654321'
|
||||
|
||||
|
||||
class Block(object):
|
||||
def __init__(self, boxes, operation, result):
|
||||
self.boxes = boxes
|
||||
self.operation = operation
|
||||
self.result = result
|
||||
|
||||
self.solutions = set()
|
||||
|
||||
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 = tuple(zip(self.boxes, perm))
|
||||
self.solutions.add(sol)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Block {}'.format(self.boxes)
|
||||
|
||||
|
||||
def translate_box_to_rc(box):
|
||||
return BOX_V_LABELS.index(box[1]), BOX_H_LABELS.index(box[0])
|
||||
|
||||
@ -118,6 +52,98 @@ def fill_grid(grid, solution):
|
||||
return grid
|
||||
|
||||
|
||||
def check_grid(grid):
|
||||
for row in grid:
|
||||
if not has_line_integrity(row):
|
||||
return False
|
||||
for col_id in range(len(grid)):
|
||||
col = [grid[r][col_id] for r in range(len(grid))]
|
||||
if not has_line_integrity(col):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def init_grid(grid_size):
|
||||
return [[0] * grid_size for _ in range(grid_size)]
|
||||
|
||||
|
||||
class Block(object):
|
||||
def __init__(self, boxes, operation, result):
|
||||
self.boxes = boxes
|
||||
self.operation = operation
|
||||
self.result = result
|
||||
|
||||
self.solutions = set()
|
||||
|
||||
self.verify()
|
||||
|
||||
def verify(self):
|
||||
"""
|
||||
Verifies that the box location are valid in the range A-F and 1-6
|
||||
Operations are in +-*/ or None.
|
||||
Where -/ have exactly 2, None has exactly 1.
|
||||
"""
|
||||
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):
|
||||
"""Returns true if all of the boxes are in the same row OR the same column"""
|
||||
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):
|
||||
"""Generates k|n combinations for 2 boxes or if they are in the same row OR column.
|
||||
Generates k|n combinations with replacements for other cases."""
|
||||
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):
|
||||
"""Generates hypothesis for this block based on the operation and result."""
|
||||
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):
|
||||
"""Generates possible solutions for the block, including the limiting row/column requirement."""
|
||||
self.generate_hypotheses(grid_size)
|
||||
for hyp in self.generate_hypotheses(grid_size):
|
||||
for perm in itertools.permutations(hyp):
|
||||
sol = tuple(zip(self.boxes, perm))
|
||||
if check_grid(fill_grid(init_grid(grid_size), sol)):
|
||||
self.solutions.add(sol)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Block {}'.format(self.boxes)
|
||||
|
||||
|
||||
class Game(object):
|
||||
def __init__(self, grid_size, blocks):
|
||||
self.blocks = blocks
|
||||
@ -127,10 +153,8 @@ class Game(object):
|
||||
for block in self.blocks:
|
||||
block.generate_solutions(self.grid_size)
|
||||
|
||||
def init_grid(self):
|
||||
return [[0] * self.grid_size for _ in range(self.grid_size)]
|
||||
|
||||
def verify(self):
|
||||
"""Check that each block is found exactly once in the grid."""
|
||||
found = set()
|
||||
for block in self.blocks:
|
||||
for box in block.boxes:
|
||||
@ -138,30 +162,17 @@ class Game(object):
|
||||
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 check_grid(self, grid):
|
||||
for row in grid:
|
||||
if not has_line_integrity(row):
|
||||
return False
|
||||
for col_id in range(self.grid_size):
|
||||
col = [grid[r][col_id] for r in range(self.grid_size)]
|
||||
if not has_line_integrity(col):
|
||||
return False
|
||||
return True
|
||||
|
||||
def solve(self, grid, current_block_id=0):
|
||||
"""
|
||||
Recursive solution - Start by filling up the first block.
|
||||
For each solution that passes the row/column requirement, check the recursive solution
|
||||
with the next block. Break when it's the last block and the row/col requirement is filled.
|
||||
"""
|
||||
block = self.blocks[current_block_id]
|
||||
for solution in block.solutions:
|
||||
prev_grid = deepcopy(grid)
|
||||
grid = fill_grid(grid, solution)
|
||||
if not self.check_grid(grid):
|
||||
if not check_grid(grid):
|
||||
grid = prev_grid
|
||||
else:
|
||||
if current_block_id == len(self.blocks) - 1:
|
||||
@ -189,7 +200,7 @@ def main():
|
||||
Block(boxes=['D1', 'E1'], operation=OP_MINUS, result=1),
|
||||
Block(boxes=['F1', 'F2', 'F3', 'E3'], operation=OP_PLUS, result=16),
|
||||
])
|
||||
grid = game.solve(grid=game.init_grid())
|
||||
grid = game.solve(grid=init_grid(game.grid_size))
|
||||
for line in grid:
|
||||
print(line)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user