diff --git a/src/solution/year_2025/day_09.py b/src/solution/year_2025/day_09.py index 2a77100..6d3f468 100644 --- a/src/solution/year_2025/day_09.py +++ b/src/solution/year_2025/day_09.py @@ -1,4 +1,3 @@ -from collections.abc import Generator from typing import Any Point = tuple[int, int] @@ -14,10 +13,7 @@ def _parse_input(input_data) -> list[Point]: def _rectangle_size(p1: Point, p2: Point) -> int: - x1, y1 = p1 - x2, y2 = p2 - - return (abs(x1 - x2) + 1) * (abs(y1 - y2) + 1) + return (abs(p1[0] - p2[0]) + 1) * (abs(p1[1] - p2[1]) + 1) def part_1(input_data: str) -> Any: @@ -47,22 +43,8 @@ def _get_dir(p1: Point, p2: Point) -> Point: return dx, dy -def _get_segment(p1: Point, p2: Point) -> Generator[Point]: - dx, dy = _get_dir(p1, p2) - - points = [] - - sx, sy = p1 - while (sx, sy) != p2: - yield sx, sy - points.append((sx, sy)) - sx, sy = sx + dx, sy + dy - - yield p2 - - def _compress_coordinates(points: list[Point], y: bool) -> dict[int, int]: - return {v: k for k, v in enumerate(sorted({p[y] for p in points}))} + return {v: k for k, v in enumerate(sorted({p[y] for p in points}), 1)} def _find_inner( @@ -80,15 +62,17 @@ def _find_inner( c1, c2 = c(p1), c(p2) - rng = _get_segment(c1, c2) - + x, y = c1 dx, dy = _get_dir(c1, c2) - # rotate 90 positive as inside is in the positive direction - dx, dy = -dy, dx + pdx, pdy = -dy, dx - for x, y in rng: + while (x, y) != c2: inner.add((x, y)) - inside.add((x + dx, y + dy)) + inside.add((x + pdx, y + pdy)) + x, y = x + dx, y + dy + + inner.add((x, y)) + inside.add((x + pdx, y + pdy)) stack = list(inside - inner) @@ -106,18 +90,6 @@ def _find_inner( return inner -def _get_corner_points(p1: Point, p2: Point) -> tuple[Point, ...]: - if p1 == p2: - return (p1,) - - px1, py1 = p1 - px2, py2 = p2 - p3 = px1, py2 - p4 = px2, py1 - - return p1, p3, p2, p4 - - def part_2(input_data: str) -> Any: points = _parse_input(input_data) @@ -129,6 +101,19 @@ def part_2(input_data: str) -> Any: inner = _find_inner(points, x_compression, y_compression) + w = max(x_compression.values()) + h = max(y_compression.values()) + + prefix = [[0] * (h + 1) for _ in range(w + 1)] + for x in range(1, w + 1): + for y in range(1, h + 1): + prefix[x][y] = ( + ((x, y) in inner) + + prefix[x - 1][y] + + prefix[x][y - 1] + - prefix[x - 1][y - 1] + ) + m = None for idx, p1 in enumerate(points): @@ -136,19 +121,28 @@ def part_2(input_data: str) -> Any: for p2 in points[idx + 1 :]: c2 = c(p2) - c1, c2, c3, c4 = _get_corner_points(c1, c2) + size = _rectangle_size(p1, p2) + if m and size <= m: + continue - is_inside = ( - all(x in inner for x in _get_segment(c1, c2)) - and all(x in inner for x in _get_segment(c2, c3)) - and all(x in inner for x in _get_segment(c3, c4)) - and all(x in inner for x in _get_segment(c4, c1)) + min_x = min(c1[0], c2[0]) + max_x = max(c1[0], c2[0]) + min_y = min(c1[1], c2[1]) + max_y = max(c1[1], c2[1]) + + c_expected_size = _rectangle_size(c1, c2) + c_size = ( + prefix[max_x][max_y] + - prefix[min_x - 1][max_y] + - prefix[max_x][min_y - 1] + + prefix[min_x - 1][min_y - 1] ) + is_inside = c_size == c_expected_size + if not is_inside: continue - size = _rectangle_size(p1, p2) m = max(m or size, size) return m