diff --git a/2024/d12.py b/2024/d12.py index c8197d1..0740b75 100755 --- a/2024/d12.py +++ b/2024/d12.py @@ -72,38 +72,27 @@ def perimeter1(self, region: set[complex]) -> int: ) def perimeter2(self, region: set[complex]) -> int: - """Walk the edge of a region and count turns.""" - area = len(region) - if area == 1: - return 4 + """Count the number of edge-runs. - perimeters = 0 - unwalked_walls = {r for r in region if any(n not in region for n in aoc.neighbors(r))} - while unwalked_walls: - cur = next(r for r in unwalked_walls if any(n not in region for n in aoc.neighbors(r))) - unwalked_walls.remove(cur) - direction = next(d for d in aoc.FOUR_DIRECTIONS if cur + d not in region) - while cur + direction not in region: - direction *= -1j - start_pos = cur - start_dir = direction - - perimeter = 0 - while cur != start_pos or direction != start_dir or perimeter < 1: - unwalked_walls.discard(cur) - if cur + direction * 1j in region: - direction *= 1j - perimeter += 1 - while cur + direction not in region: - direction *= -1j - perimeter += 1 - if cur == start_pos and direction == start_dir and perimeter: - break - if cur + direction in region: - cur += direction - unwalked_walls.discard(cur) - perimeters += perimeter - return perimeters + This can be computed by counting the number of "corners" a region has. + A point is a convex corner if it no neighbors in two adjacent directions (eg up and right). + A point is a concave corner if there are neighbors in + two adjacent directions (eg up and right) but not diagonal in those directions. + """ + d1, d2, d3 = complex(1, 0), complex(0, 1), complex(1, 1) + convex_corners = sum( + 1 + for coord in region + for rotation in aoc.FOUR_DIRECTIONS + if coord + d1 * rotation not in region and coord + d2 * rotation not in region + ) + concave_corners = sum( + 1 + for coord in region + for rotation in aoc.FOUR_DIRECTIONS + if coord + d1 * rotation in region and coord + d2 * rotation in region and coord + d3 * rotation not in region + ) + return convex_corners + concave_corners def solver(self, puzzle_input: aoc.Map, part_one: bool) -> int: perimeter = self.perimeter1 if part_one else self.perimeter2 diff --git a/pylib/aoc.py b/pylib/aoc.py index 583088d..eabfc34 100644 --- a/pylib/aoc.py +++ b/pylib/aoc.py @@ -509,19 +509,6 @@ def reading_order(data: Sequence[complex]) -> list[complex]: return sorted(data, key=lambda x: (x.imag, x.real)) -def partition_regions(data: dict[complex, T], directions: list[complex] = FOUR_DIRECTIONS) -> list[tuple[T, set[complex]]]: - """Partition a map into regions.""" - regions = [] - todo = set(data) - while todo: - cur = todo.pop() - val = data[cur] - region = floodfill(data, cur) - todo -= region - regions.append((val, region)) - return regions - - def floodfill(data: dict[complex, T], start: complex, directions: list[complex] = FOUR_DIRECTIONS) -> set[complex]: """Expand a point to its region by flood filling so long as the char matches.""" todo = {start} @@ -537,6 +524,19 @@ def floodfill(data: dict[complex, T], start: complex, directions: list[complex] return region +def partition_regions(data: dict[complex, T], directions: list[complex] = FOUR_DIRECTIONS) -> list[tuple[T, set[complex]]]: + """Partition a map into regions.""" + regions = [] + todo = set(data) + while todo: + cur = todo.pop() + val = data[cur] + region = floodfill(data, cur, directions) + todo -= region + regions.append((val, region)) + return regions + + def interval_overlap(one: Interval, two: Interval) -> tuple[Interval | None, Interval | None, Interval | None]: one_start, one_end = one two_start, two_end = two