with open('test.text', 'r') as file: data: str = file.read() gardens: list[list[str]] = [[c for c in line.strip()] for line in data.split('\n')] x_bound: int = len(gardens[0]) - 1 y_bound: int = len(gardens) - 1 class Coord: x: int y: int def __init__(self, x: int, y: int): self.x = x self.y = y def __str__(self) -> str: return f'({self.x}, {self.y})' def __eq__(self, other) -> bool: return other.x == self.x and other.y == self.y def __add__(self, other): return Coord(self.x + other.x, self.y + other.y) def __sub__(self, other): return Coord(self.x - other.x, self.y - other.y) def in_bounds(c: Coord) -> bool: if c.y > y_bound or c.y < 0: return False if c.x > x_bound or c.x < 0: return False return True def neighbor_count(c: Coord) -> int: neighbors = 0 up, down, left, right = Coord(c.x, c.y - 1), Coord(c.x, c.y + 1), Coord(c.x - 1, c.y), Coord(c.x + 1, c.y) v: str = gardens[c.y][c.x] for direction in [up, down, left, right]: if in_bounds(direction): if gardens[direction.y][direction.x] == v: neighbors += 1 return neighbors # Uses a flood fill to find each region, and returns a region as a list of coordinates def flood_fill(start: Coord) -> list[Coord]: filled: list[Coord] = [] to_fill: list[Coord] = [start] edges: list[tuple[int, Coord]] = [] # Each edge is a tuple of (direction unit vector, x or y depending on if left, right or up, down) v: str = gardens[start.y][start.x] while to_fill: c = to_fill.pop() filled.append(c) up, down, left, right = Coord(c.x, c.y - 1), Coord(c.x, c.y + 1), Coord(c.x - 1, c.y), Coord(c.x + 1, c.y) for direction in [up, down, left, right]: if in_bounds(direction): if gardens[direction.y][direction.x] == v: if direction not in filled and direction not in to_fill: to_fill.append(direction) continue unit_direction = c - direction # Means it's up/down if unit_direction.x == 0: if (c.y, unit_direction) not in edges: edges.append((c.y, unit_direction)) # Left / Right else: if (c.x, unit_direction) not in edges: edges.append((c.x, unit_direction)) print(v, len(edges)) for edge in edges: print(edge[0], edge[1]) return filled def print_region(region: list[Coord]) -> None: for member in region: print(member, end=' ') print() for y, line in enumerate(gardens): for x, char in enumerate(line): print(char if Coord(x, y) in region else ' ', end='') print() # Part 1 total_cost = 0 checked = gardens.copy() for y, line in enumerate(gardens): for x, char in enumerate(line): if checked[y][x] == "-1": continue region = flood_fill(Coord(x, y)) area = len(region) perimeter = 0 for member in region: perimeter += 4 - neighbor_count(member) for member in region: checked[member.y][member.x] = '-1' total_cost += area * perimeter # print(f'A region of {char} plants with price {area} * {perimeter} = {area * perimeter}') print(f'Total Cost: {total_cost}')