feat: add solution 2025/09
This commit is contained in:
8
data/example/2025/09.txt
Normal file
8
data/example/2025/09.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
7,1
|
||||||
|
11,1
|
||||||
|
11,7
|
||||||
|
9,7
|
||||||
|
9,5
|
||||||
|
2,5
|
||||||
|
2,3
|
||||||
|
7,3
|
||||||
162
src/solution/year_2025/day_09.py
Normal file
162
src/solution/year_2025/day_09.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
from collections.abc import Generator
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
Point = tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_input(input_data) -> list[Point]:
|
||||||
|
points = []
|
||||||
|
for line in input_data.splitlines():
|
||||||
|
a, b = line.split(",")
|
||||||
|
points.append((int(a), int(b)))
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def _rectangle_size(p1: Point, p2: Point) -> int:
|
||||||
|
x1, y1 = p1
|
||||||
|
x2, y2 = p2
|
||||||
|
|
||||||
|
return (abs(x1 - x2) + 1) * (abs(y1 - y2) + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(input_data: str) -> Any:
|
||||||
|
lines = _parse_input(input_data)
|
||||||
|
|
||||||
|
m = None
|
||||||
|
for idx, p1 in enumerate(lines):
|
||||||
|
for p2 in lines[idx + 1 :]:
|
||||||
|
size = _rectangle_size(p1, p2)
|
||||||
|
m = max(size, m or size)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def _get_dir(p1: Point, p2: Point) -> Point:
|
||||||
|
x1, y1 = p1
|
||||||
|
x2, y2 = p2
|
||||||
|
|
||||||
|
dx = x2 - x1
|
||||||
|
dy = y2 - y1
|
||||||
|
|
||||||
|
if dx:
|
||||||
|
dx = dx // abs(dx)
|
||||||
|
if dy:
|
||||||
|
dy = dy // abs(dy)
|
||||||
|
|
||||||
|
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}))}
|
||||||
|
|
||||||
|
|
||||||
|
def _find_inner(
|
||||||
|
points: list[Point], xc: dict[int, int], yc: dict[int, int]
|
||||||
|
) -> set[Point]:
|
||||||
|
def c(p: Point) -> Point:
|
||||||
|
return xc[p[0]], yc[p[1]]
|
||||||
|
|
||||||
|
inner = set()
|
||||||
|
inside = set()
|
||||||
|
|
||||||
|
for idx in range(len(points)):
|
||||||
|
p1 = points[idx - 1]
|
||||||
|
p2 = points[idx]
|
||||||
|
|
||||||
|
c1, c2 = c(p1), c(p2)
|
||||||
|
|
||||||
|
rng = _get_segment(c1, c2)
|
||||||
|
|
||||||
|
dx, dy = _get_dir(c1, c2)
|
||||||
|
# rotate 90 positive as inside is in the positive direction
|
||||||
|
dx, dy = -dy, dx
|
||||||
|
|
||||||
|
for x, y in rng:
|
||||||
|
inner.add((x, y))
|
||||||
|
inside.add((x + dx, y + dy))
|
||||||
|
|
||||||
|
stack = list(inside - inner)
|
||||||
|
|
||||||
|
dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
|
||||||
|
while stack:
|
||||||
|
px, py = stack.pop()
|
||||||
|
inner.add((px, py))
|
||||||
|
|
||||||
|
for dx, dy in dirs:
|
||||||
|
nx, ny = px + dx, py + dy
|
||||||
|
if (nx, ny) in inner:
|
||||||
|
continue
|
||||||
|
stack.append((nx, ny))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
x_compression = _compress_coordinates(points, False)
|
||||||
|
y_compression = _compress_coordinates(points, True)
|
||||||
|
|
||||||
|
def c(p: Point) -> Point:
|
||||||
|
return x_compression[p[0]], y_compression[p[1]]
|
||||||
|
|
||||||
|
inner = _find_inner(points, x_compression, y_compression)
|
||||||
|
|
||||||
|
m = None
|
||||||
|
|
||||||
|
for idx, p1 in enumerate(points):
|
||||||
|
c1 = c(p1)
|
||||||
|
for p2 in points[idx + 1 :]:
|
||||||
|
c2 = c(p2)
|
||||||
|
|
||||||
|
c1, c2, c3, c4 = _get_corner_points(c1, c2)
|
||||||
|
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_inside:
|
||||||
|
continue
|
||||||
|
|
||||||
|
size = _rectangle_size(p1, p2)
|
||||||
|
m = max(m or size, size)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def test_part_1(example_data):
|
||||||
|
assert part_1(example_data) == 50
|
||||||
|
|
||||||
|
|
||||||
|
def test_part_2(example_data):
|
||||||
|
assert part_2(example_data) == 24
|
||||||
Reference in New Issue
Block a user