Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import itertools
- import time
- def test():
- board = Board(2,
- {"colours": [[1, 1, 1], [1, 1, 2], [2, 2, 3]],
- "trees": {(0, 2)}},
- False)
- assert powerset([1, 2, 3]) == {(1,), (2,), (3,), (1, 2), (1, 3), (2, 3)}
- assert board.get_neighbours((1, 1)) == {(0, 0), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1), (2, 2)}
- assert board.get_neighbours((0, 2)) == {(0, 1), (1, 1), (1, 2)}
- assert board.row_index((0, 2)) == 0
- assert board.column_index((0, 2)) == 2
- assert board.row_column_min({(0, 2), (1, 1)}) == (0, 1)
- assert board.row_column_max({(0, 2), (1, 1)}) == (1, 2)
- assert board.quads == [{(0, 0), (1, 0), (0, 1), (1, 1)},
- {(0, 1), (1, 1), (0, 2), (1, 2)},
- {(1, 0), (2, 0), (1, 1), (2, 1)},
- {(1, 1), (2, 1), (1, 2), (2, 2)}]
- assert board.quad_sets[2] == [({(0, 1), (1, 0), (0, 0), (1, 1)}, {(0, 1), (0, 2), (1, 1), (1, 2)}),
- ({(0, 1), (1, 0), (0, 0), (1, 1)}, {(2, 0), (1, 0), (1, 1), (2, 1)}),
- ({(0, 1), (1, 0), (0, 0), (1, 1)}, {(1, 2), (1, 1), (2, 1), (2, 2)}),
- ({(0, 1), (0, 2), (1, 1), (1, 2)}, {(2, 0), (1, 0), (1, 1), (2, 1)}),
- ({(0, 1), (0, 2), (1, 1), (1, 2)}, {(1, 2), (1, 1), (2, 1), (2, 2)}),
- ({(2, 0), (1, 0), (1, 1), (2, 1)}, {(1, 2), (1, 1), (2, 1), (2, 2)})]
- assert board.identify_sub_regions({(2, 0), (2, 2)}, 2) == ({(2, 0), (1, 0), (1, 1), (2, 1)},
- {(1, 2), (1, 1), (2, 1), (2, 2)})
- assert board.identify_sub_regions({(2, 0), (2, 2)}, 2) == ({(2, 0), (1, 0), (1, 1), (2, 1)},
- {(1, 2), (1, 1), (2, 1), (2, 2)})
- assert board.rows == {0: {(0, 0), (0, 1), (0, 2)}, 1: {(1, 0), (1, 1), (1, 2)}, 2: {(2, 0), (2, 1), (2, 2)}}
- assert board.columns == {0: {(0, 0), (1, 0), (2, 0)}, 1: {(0, 1), (1, 1), (2, 1)}, 2: {(0, 2), (1, 2), (2, 2)}}
- assert board.colour_regions == {1: {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)},
- 2: {(1, 2), (2, 0), (2, 1)},
- 3: {(2, 2)}}
- assert board.count_trees(board.get_neighbours((0, 2))) == 0
- assert board.count_trees({(0, 2)}) == 1
- assert board.common_neighbours({(0, 0), (1, 1)}) == {(0, 1), (1, 0)}
- board1 = Board(1,
- {"colours": [[1, 1, 2, 3, 4],
- [1, 1, 2, 2, 4],
- [5, 1, 2, 4, 4],
- [5, 5, 5, 4, 4],
- [5, 5, 5, 5, 5]]},
- False)
- solution1 = {(0, 3), (1, 0), (2, 2), (3, 4), (4, 1)}
- print("Solve board1 ------------------------------------------------------------------------------------")
- board1.find_trees()
- assert board1.trees == solution1
- board2 = Board(1,
- {"colours": [[1, 1, 1, 1, 1],
- [2, 1, 1, 1, 3],
- [1, 1, 1, 4, 3],
- [1, 5, 4, 4, 3],
- [1, 5, 4, 4, 4]]},
- False)
- solution2 = {(0, 2), (1, 0), (2, 4), (3, 1), (4, 3)}
- print("Solve board2 -----------------------------------------------------------------------------------")
- board2.find_trees()
- assert board2.trees == solution2
- board3 = Board(1,
- {"colours": [[1, 1, 2, 2, 2],
- [1, 1, 2, 2, 2],
- [3, 2, 2, 2, 2],
- [3, 4, 4, 5, 5],
- [3, 3, 5, 5, 5]]},
- False)
- solution3 = {(0, 1), (1, 3), (2, 0), (3, 2), (4, 4)}
- print("Solve board3 ---------------------------------------------------------------------------------")
- board3.find_trees()
- assert board3.trees == solution3
- board14 = Board(1,
- {"colours": [[1, 1, 1, 1, 2, 2, 2],
- [1, 1, 3, 1, 4, 4, 2],
- [1, 5, 3, 6, 6, 4, 2],
- [1, 5, 3, 3, 6, 4, 4],
- [1, 5, 5, 3, 6, 7, 7],
- [1, 1, 7, 7, 7, 7, 7],
- [7, 7, 7, 7, 7, 7, 7]]},
- False)
- solution14 = {(0, 6), (1, 2), (2, 5), (3, 1), (4, 4), (5, 0), (6, 3)}
- print("Solve board14 ---------------------------------------------------------------------------------")
- board14.find_trees()
- assert board14.trees == solution14
- board39 = Board(2,
- {"colours": [[1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 4, 4],
- [1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 4, 4],
- [1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4],
- [5, 5, 5, 1, 6, 6, 6, 7, 2, 7, 8, 8],
- [5, 5, 1, 1, 6, 6, 6, 7, 2, 7, 8, 8],
- [5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8],
- [5, 5, 5, 6, 9, 10, 6, 6, 7, 8, 8, 8],
- [9, 9, 5, 9, 9, 10, 10, 10, 10, 11, 8, 8],
- [9, 9, 9, 9, 9, 10, 10, 10, 10, 11, 8, 11],
- [12, 12, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11],
- [12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11],
- [12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11]]},
- False)
- solution39 = {(5, 9), (6, 11), (9, 3), (8, 0), (6, 0), (2, 1), (2, 10), (10, 1), (4, 2), (7, 2), (10, 8),
- (4, 4), (5, 7), (8, 10), (11, 3), (3, 8), (1, 5), (0, 11), (7, 4), (3, 6), (1, 7), (0, 9),
- (9, 5), (11, 6)}
- print("Solve Board39 --------------------------------------------------------------------------------")
- board39.find_trees()
- assert board39.trees == solution39
- board41 = Board(2,
- {"colours": [[1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3],
- [1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4],
- [1, 1, 2, 2, 2, 5, 3, 3, 3, 4, 4, 4],
- [1, 1, 2, 2, 2, 5, 3, 3, 4, 4, 4, 4],
- [1, 1, 2, 2, 2, 5, 5, 4, 4, 4, 4, 4],
- [1, 6, 6, 7, 7, 7, 7, 7, 4, 8, 4, 4],
- [6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8],
- [6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9],
- [6, 6, 6, 6, 10, 10, 8, 8, 11, 11, 11, 9],
- [6, 6, 6, 10, 10, 10, 11, 11, 11, 11, 9, 9],
- [6, 6, 6, 6, 10, 10, 11, 11, 11, 11, 9, 9],
- [6, 6, 6, 10, 10, 10, 10, 12, 12, 12, 9, 9]]},
- False)
- solution41 = {(0, 4), (0, 6), (1, 0), (1, 2), (2, 5), (2, 10), (3, 0), (3, 2), (4, 6), (4, 8),
- (5, 1), (5, 3), (6, 8), (6, 10), (7, 1), (7, 4), (8, 9), (8, 11), (9, 3), (9, 7),
- (10, 5), (10, 11), (11, 7), (11, 9)}
- print("Solve board41 --------------------------------------------------------------------------------")
- board41.find_trees()
- assert board41.trees == solution41
- return "Tests pass"
- class Board:
- """
- """
- def __init__(self, trees_per_region, cell_data, print_path):
- self.colours = cell_data["colours"]
- self.colour_regions = self.get_colour_regions(cell_data["colours"])
- self.colour_region_combinations = powerset(self.colour_regions.keys())
- self.num_rows = len(cell_data["colours"])
- self.num_columns = len(cell_data["colours"][0])
- self.num_colour_regions = len(self.colour_regions.keys())
- self.trees_per_row = trees_per_region
- self.trees_per_column = trees_per_region
- self.trees_per_colour_region = trees_per_region
- self.max_trees_per_region = max(self.trees_per_row, self.trees_per_column, self.trees_per_colour_region)
- self.total_trees = self.check_total_trees()
- self.trees = set()
- self.nos = set()
- self.trees = self.mark_trees(cell_data.get("trees", set()))
- self.nos = self.mark_no_trees(cell_data.get("nos", set()))
- self.rows = {i: set([(i, j) for j in range(self.num_columns)]) for i in range(self.num_rows)}
- self.columns = {j: set([(i, j) for i in range(self.num_rows)]) for j in range(self.num_columns)}
- self.quads = [{(i, j), (i + 1, j), (i, j + 1), (i + 1, j + 1)}
- for i in range(self.num_rows - 1)
- for j in range(self.num_columns - 1)]
- self.quad_sets = {r: list(itertools.combinations(self.quads, r))
- for r in range(2, self.max_trees_per_region * 2 + 1)}
- self.print_path = print_path
- def find_trees(self):
- start_time = time.time()
- print("Finding Trees")
- while len(self.trees) < self.total_trees:
- found_cells = (set(self.trees), set(self.nos)) # set() functions needed to create proper copies of data
- self.run_checks()
- if (self.trees, self.nos) == found_cells:
- print("Not all trees found, failing to progress. "
- "Aborted after {0} seconds".format(time.time() - start_time))
- print("Trees found:")
- print(self.trees)
- print("Nos found:")
- print(self.nos)
- return self.trees
- print("Solution found after {0} seconds".format(time.time() - start_time))
- print(self.trees)
- return self.trees
- def run_checks(self):
- # Check each row, column and colour_region for basic tree total and neighbourhood constraints
- for region in self.colour_regions.keys():
- self.colour_regions[region] = self.check_region(self.colour_regions[region], self.trees_per_colour_region)
- self.check_sub_regions(self.colour_regions[region], self.trees_per_colour_region)
- for region in self.rows.keys():
- self.rows[region] = self.check_region(self.rows[region], self.trees_per_row)
- self.check_sub_regions(self.rows[region], self.trees_per_row)
- for region in self.columns.keys():
- self.columns[region] = self.check_region(self.columns[region], self.trees_per_column)
- self.check_sub_regions(self.columns[region], self.trees_per_column)
- for comb in self.colour_region_combinations:
- combined_region = set().union(*[self.colour_regions[index] for index in comb])
- trees_in_combined_region = len(comb) * self.trees_per_colour_region
- self.check_subsets(combined_region, trees_in_combined_region, 3)
- def check_region(self, region, n):
- """
- Return the region excluding any nos after running basic tree total and neighbourhood constraint checks
- :param region: A region to be checked against the condition of containing n trees
- :param n: The number of trees in total that should exist within the given region
- :return: A subset of the input region excluding any nos
- """
- if self.count_unknowns(region) == 0 and n == 0: return region
- if self.print_path: print("Checking region " + str(region) + ", " + str(n))
- # Check whether region is over populated and raise error
- if self.count_trees(region) > n:
- print("Found {0} trees in region but expected {1}".format(self.count_trees(region), n))
- print("Trees {0} found in region {1}".format(self.trees & region, region))
- raise ValueError("Too many trees found in region")
- # Check whether region is under populated and raise error
- if n - self.count_trees(region) > self.count_unknowns(region):
- print("Found {0} trees in region and {1} unknowns. "
- "Expected {2} trees in total".format(self.count_trees(region), self.count_unknowns(region), n))
- print("Trees {0} found in region {1}".format(self.trees & region, region))
- raise ValueError("Too few trees found in region")
- # Check whether region is already complete with correct number of trees
- # and mark all remaining unknown cells as nos
- unknowns = self.unknowns_in_region(region)
- if unknowns:
- if self.count_trees(region) == n:
- print("Region complete, mark remaining to no. Region {0}".format(region))
- self.mark_no_trees(unknowns)
- # Check whether the remaining unknown cells are all required for the remaining number of trees
- # needed in the region and if so mark them all as trees
- unknowns = self.unknowns_in_region(region)
- if unknowns:
- if n - self.count_trees(region) == self.count_unknowns(region):
- print("All remaining unknowns must be trees in region {0}".format(region))
- self.mark_trees(self.unknowns_in_region(region))
- # Check whether any cells are neighbours for all remaining unknown cells in the region and mark them as nos
- # If there are any unknown cells then they must contain a tree amongst them, if not they would be marked as nos
- # Any cell that neighbours all of these unknown cells must therefore neighbour a tree and so must be a no
- unknowns = self.unknowns_in_region(region)
- if unknowns:
- common_neighbours = self.unknowns_in_region(self.common_neighbours(unknowns))
- if common_neighbours:
- print("Remove all common neighbours to unknown cells {}".format(unknowns))
- self.mark_no_trees(common_neighbours)
- # If more than one tree is still needed in a region
- # then exclude any cells within the region that boarder all other unknown cells
- # Identify these cells as being those that are neighbours of the max and min row and column index cells
- if n - self.count_trees(region) > 1:
- unknowns = self.unknowns_in_region(region)
- interior_neighbours = {cell for cell in unknowns
- if {self.row_column_min(unknowns),
- self.row_column_max(unknowns)}.issubset(self.get_neighbours(cell))}
- if interior_neighbours:
- print("Remove interior neighbours from region {0}".format(region))
- self.mark_no_trees(interior_neighbours)
- return region - self.nos_in_region(region)
- def check_sub_regions(self, region, n):
- """
- :param region:
- :param n: The number of trees in total that should exist within the given region
- :return:
- """
- if self.print_path: print("Checking sub-regions " + str(region) + ", " + str(n))
- num_remaining_trees = n - self.count_trees(region)
- sub_regions = self.identify_sub_regions(self.unknowns_in_region(region), num_remaining_trees)
- if sub_regions:
- for sub_region in sub_regions:
- self.check_region(sub_region & region, 1)
- return None
- def identify_sub_regions(self, region, num_sub_regions):
- # Exit if existence is impossible
- if len(region) > 4 * num_sub_regions:
- return None
- # Exit if only one or no sub_regions needed
- if num_sub_regions <= 1:
- return None
- # Identify a set of num_remaining_trees sub_regions that cover the region
- quad_sets = self.quad_sets.get(num_sub_regions, [])
- for quad_set in quad_sets:
- if region.issubset(set().union(*quad_set)):
- if self.print_path: print("Sub-regions identified " + str(quad_set))
- return quad_set
- def check_subsets(self, region, n, iterations):
- self.check_subsets_generic(region, n, iterations, "colour-region",
- self.colour_region, self.colour_regions, self.trees_per_colour_region)
- self.check_subsets_generic(region, n, iterations, "row",
- self.row_index, self.rows, self.trees_per_row)
- self.check_subsets_generic(region, n, iterations, "column",
- self.column_index, self.columns, self.trees_per_column)
- def check_subsets_generic(self, region, n, iterations,
- region_type, region_type_index, region_type_dict, trees_per_region_type):
- iterations += -1
- if iterations < 1: return None
- if self.print_path: print("Checking " + region_type + " subsets "
- + str(iterations) + str(region) + ", " + str(n))
- superset_indices = set([region_type_index(cell) for cell in self.unknowns_in_region(region)])
- smallest_superset = set().union(*[region_type_dict[i] for i in superset_indices])
- trees_in_superset = len(superset_indices) * trees_per_region_type
- residual_region = smallest_superset - region
- trees_in_residual = trees_in_superset - n + self.count_trees(region - smallest_superset)
- if residual_region:
- if self.print_path: print("Smallest superset " + str(smallest_superset) + ", " + str(trees_in_superset))
- self.check_region(residual_region, trees_in_residual)
- self.check_sub_regions(residual_region, trees_in_residual)
- self.check_subsets(residual_region, trees_in_residual, iterations)
- subset_indices = {i for i in superset_indices
- if self.unknowns_in_region(region_type_dict[i]).issubset(region)}
- largest_subset = set().union(*[region_type_dict[i] for i in subset_indices])
- trees_in_subset = len(subset_indices) * trees_per_region_type
- residual_region = region & smallest_superset - largest_subset
- trees_in_residual = n - self.count_trees(region - smallest_superset) - (
- trees_in_subset - self.count_trees(largest_subset - region))
- if residual_region:
- if self.print_path: print("Largest subset " + str(largest_subset) + ", " + str(trees_in_subset))
- self.check_region(residual_region, trees_in_residual)
- self.check_sub_regions(residual_region, trees_in_residual)
- self.check_subsets(residual_region, trees_in_residual, iterations)
- return None
- def check_total_trees(self):
- total_trees = self.num_rows * self.trees_per_row
- if (total_trees == self.num_columns * self.trees_per_column and
- total_trees == self.num_colour_regions * self.trees_per_colour_region):
- return total_trees
- else:
- raise ValueError("Board regions inconsistent")
- def check_is_tree(self, cell):
- return cell in self.trees
- def check_no_tree(self, cell):
- return cell in self.nos
- def check_unknown_tree(self, cell):
- return not self.check_is_tree(cell) and not self.check_no_tree(cell)
- def mark_tree(self, cell):
- if self.check_no_tree(cell):
- raise ValueError("Changed mind no to tree {0}".format(cell))
- if self.check_is_tree(cell):
- print("Already known tree {0}".format(cell))
- else:
- self.trees.update({cell})
- print("Set tree {0}".format(cell))
- unknown_neighbours = self.unknowns_in_region(self.get_neighbours(cell))
- if unknown_neighbours:
- print("Set neighbours to no {0}".format(cell))
- self.mark_no_trees(unknown_neighbours)
- def mark_trees(self, region):
- for cell in region:
- self.mark_tree(cell)
- return self.trees
- def mark_no_tree(self, cell):
- if self.check_is_tree(cell):
- raise ValueError("Changed mind tree to no {0}".format(cell))
- if self.check_no_tree(cell):
- print("Already known no {0}".format(cell))
- else:
- print("Set no {0}".format(cell))
- self.nos.update({cell})
- def mark_no_trees(self, region):
- for cell in region:
- self.mark_no_tree(cell)
- return self.nos
- def get_colour_regions(self, cell_colours):
- colour_regions = {k: set() for k in set().union(*cell_colours)}
- for i in range(len(cell_colours)):
- for j in range(len(cell_colours[i])):
- colour_regions[cell_colours[i][j]].update({(i, j)})
- return colour_regions
- def get_neighbours(self, cell):
- neighbours = {(i, j) for i in range(max(cell[0] - 1, 0), min(cell[0] + 2, self.num_rows))
- for j in range(max(cell[1] - 1, 0), min(cell[1] + 2, self.num_columns))}
- neighbours.remove(cell)
- return neighbours
- def common_neighbours(self, region):
- neighbourhoods = [self.get_neighbours(cell) for cell in region]
- return set.intersection(*neighbourhoods)
- def colour_region(self, cell):
- return self.colours[cell[0]][cell[1]]
- def row_index(self, cell):
- return cell[0]
- def column_index(self, cell):
- return cell[1]
- def row_column_min(self, region):
- """
- :param region:
- :return: A cell reference (not necessarily lying in the region) which bounds the region at the top, left.
- """
- return min([cell[0] for cell in region]), min([cell[1] for cell in region])
- def row_column_max(self, region):
- """
- :param region:
- :return: A cell reference (not necessarily lying in the region) which bounds the region at the bottom, right.
- """
- return max([cell[0] for cell in region]), max([cell[1] for cell in region])
- def trees_in_region(self, region):
- return self.trees & region
- def nos_in_region(self, region):
- return self.nos & region
- def unknowns_in_region(self, region):
- return region - (self.trees_in_region(region) | self.nos_in_region(region))
- def count_trees(self, region):
- return len(self.trees_in_region(region))
- def count_nos(self, region):
- return len(self.nos_in_region(region))
- def count_unknowns(self, region):
- return len(self.unknowns_in_region(region))
- def powerset(iterable):
- """
- powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
- :param iterable:
- :return:
- """
- s = list(iterable)
- return set(itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(1, len(s))))
- test()
Add Comment
Please, Sign In to add comment