diff --git a/src/holt59/aoc/2015/day22.py b/src/holt59/aoc/2015/day22.py index 6d1456f..d21929e 100644 --- a/src/holt59/aoc/2015/day22.py +++ b/src/holt59/aoc/2015/day22.py @@ -173,7 +173,6 @@ class Solver(BaseSolver): ) ) - # 1242 (not working) yield sum( c for _, c in play( diff --git a/src/holt59/aoc/2024/day12.py b/src/holt59/aoc/2024/day12.py index 6061a69..e87e9c4 100644 --- a/src/holt59/aoc/2024/day12.py +++ b/src/holt59/aoc/2024/day12.py @@ -1,3 +1,5 @@ +import itertools as it +from dataclasses import dataclass from typing import Any, Iterator, TypeAlias from ..base import BaseSolver @@ -6,133 +8,94 @@ Node: TypeAlias = tuple[int, int] Edge: TypeAlias = tuple[Node, Node] +@dataclass(frozen=True) +class Region: + value: str + cells: set[tuple[int, int]] + edges: set[Edge] + sides: list[tuple[Edge, ...]] + + +def extract_region(grid: list[str], cell: tuple[int, int]): + n_rows, n_cols = len(grid), len(grid[0]) + row, col = cell + + value = grid[row][col] + + cells: set[tuple[int, int]] = set() + edges: set[Edge] = set() + sides: list[tuple[Edge, ...]] = [] + + queue: list[tuple[int, int]] = [(row, col)] + while queue: + row, col = queue.pop(0) + + if (row, col) in cells: + continue + + cells.add((row, col)) + + for ur, uc in ( + (row - 1, col), + (row, col + 1), + (row + 1, col), + (row, col - 1), + ): + if 0 <= ur < n_rows and 0 <= uc < n_cols and grid[ur][uc] == value: + queue.append((ur, uc)) + continue + + if ((row, col), (ur, uc)) in edges: + continue + + if col == uc: + mid, max = col, n_cols + + def get(v: int): + return (row, v, ur, v) + else: + mid, max = row, n_rows + + def get(v: int): + return (v, col, v, uc) + + side: tuple[Edge, ...] = ((((row, col), (ur, uc))),) + for rng in (range(mid - 1, -1, -1), range(mid, max)): + for r2, c2, ur2, uc2 in map(get, rng): + if grid[r2][c2] != value or ( + 0 <= ur2 < n_rows + and 0 <= uc2 < n_cols + and grid[r2][c2] == grid[ur2][uc2] + ): + break + side += ((((r2, c2), (ur2, uc2))),) + + sides.append(side) + edges = edges.union(side) + + return Region(value=value, cells=cells, edges=edges, sides=sides) + + class Solver(BaseSolver): def solve(self, input: str) -> Iterator[Any]: grid = input.splitlines() - n_rows, n_cols = len(grid), len(grid[0]) - marked = {(row, col): False for row in range(n_rows) for col in range(n_cols)} + regions: list[Region] = [] + to_visit: set[tuple[int, int]] = set( + it.product(range(len(grid)), range(len(grid[0]))) + ) - total_1: int = 0 - total_2: int = 0 - - while not all(marked.values()): - row, col = next(k for k, v in marked.items() if not v) - - cells: set[tuple[int, int]] = set() - perimeter: int = 0 - value = grid[row][col] - queue: list[tuple[int, int]] = [(row, col)] - - edges: set[Edge] = set() - sides: list[tuple[Edge, ...]] = [] - - while queue: - row, col = queue.pop(0) - - if marked[row, col]: - continue - - cells.add((row, col)) - marked[row, col] = True - - if row == 0 or grid[row - 1][col] != value: - perimeter += 1 - - if ((row, col), (row - 1, col)) not in edges: - side: list[Edge] = [(((row, col), (row - 1, col)))] - for col2 in list(range(col - 1, -1, -1)): - if grid[row][col2] != value or ( - row > 0 and grid[row][col2] == grid[row - 1][col2] - ): - break - side.append((((row, col2), (row - 1, col2)))) - for col2 in range(col + 1, n_cols): - if grid[row][col2] != value or ( - row > 0 and grid[row][col2] == grid[row - 1][col2] - ): - break - side.append((((row, col2), (row - 1, col2)))) - sides.append(tuple(side)) - edges = edges.union(side) - else: - queue.append((row - 1, col)) - - if col == 0 or grid[row][col - 1] != value: - perimeter += 1 - - if ((row, col), (row, col - 1)) not in edges: - side: list[Edge] = [(((row, col), (row, col - 1)))] - for row2 in range(row - 1, -1, -1): - if grid[row2][col] != value or ( - col > 0 and grid[row2][col] == grid[row2][col - 1] - ): - break - side.append((((row2, col), (row2, col - 1)))) - for row2 in range(row + 1, n_rows): - if grid[row2][col] != value or ( - col > 0 and grid[row2][col] == grid[row2][col - 1] - ): - break - side.append((((row2, col), (row2, col - 1)))) - sides.append(tuple(side)) - edges = edges.union(side) - else: - queue.append((row, col - 1)) - - if row + 1 == n_rows or grid[row + 1][col] != value: - perimeter += 1 - - if ((row, col), (row + 1, col)) not in edges: - side: list[Edge] = [(((row, col), (row + 1, col)))] - for col2 in list(range(col - 1, -1, -1)): - if grid[row][col2] != value or ( - row + 1 < n_rows - and grid[row][col2] == grid[row + 1][col2] - ): - break - side.append((((row, col2), (row + 1, col2)))) - for col2 in range(col + 1, n_cols): - if grid[row][col2] != value or ( - row + 1 < n_rows - and grid[row][col2] == grid[row + 1][col2] - ): - break - side.append((((row, col2), (row + 1, col2)))) - sides.append(tuple(side)) - edges = edges.union(side) - else: - queue.append((row + 1, col)) - - if col + 1 == n_cols or grid[row][col + 1] != value: - perimeter += 1 - - if ((row, col), (row, col + 1)) not in edges: - side: list[Edge] = [(((row, col), (row, col + 1)))] - for row2 in range(row - 1, -1, -1): - if grid[row2][col] != value or ( - col + 1 < n_cols - and grid[row2][col] == grid[row2][col + 1] - ): - break - side.append((((row2, col), (row2, col + 1)))) - for row2 in range(row + 1, n_rows): - if grid[row2][col] != value or ( - col + 1 < n_cols - and grid[row2][col] == grid[row2][col + 1] - ): - break - side.append((((row2, col), (row2, col + 1)))) - sides.append(tuple(side)) - edges = edges.union(side) - else: - queue.append((row, col + 1)) + while to_visit: + region = extract_region(grid, next(iter(to_visit))) self.logger.info( - f"region with {value}: {len(cells)} * {perimeter} = {len(cells) * perimeter}, {len(cells)} * {len(sides)} = {len(cells) * len(sides)}" + f"region with {region.value}: " + f"{len(region.cells)} * {len(region.edges)} = {len(region.cells) * len(region.edges)}, " + f"{len(region.cells)} * {len(region.sides)} = {len(region.cells) * len(region.sides)}" ) - total_1 += len(cells) * perimeter - total_2 += len(cells) * len(sides) - yield total_1 - yield total_2 + to_visit.difference_update(region.cells) + regions.append(region) + + yield sum(len(region.cells) * len(region.edges) for region in regions) + yield sum(len(region.cells) * len(region.sides) for region in regions)