aoc2023/day18.py

102 lines
3.4 KiB
Python

from tools.aoc import AOCDay
from tools.coordinate import Coordinate
from typing import Any, Iterable
DIRECTIONS = [Coordinate(1, 0), Coordinate(0, 1), Coordinate(-1, 0), Coordinate(0, -1)]
DIR_TRANS = {'R': 0, 'D': 1, 'L': 2, 'U': 3}
def get_borders(coords: set[tuple[Coordinate, Coordinate]]) -> (int, int, int, int):
"""min_x, max_x, min_y, max_y"""
minX = list(sorted(coords))[0][0][0]
minY = list(sorted(coords, key=lambda d: (d[0][1], d[0][0])))[0][0][1]
maxX = list(sorted(coords, reverse=True))[0][0][0]
maxY = list(sorted(coords, key=lambda d: (d[0][1], d[0][0]), reverse=True))[0][0][1]
return minX, maxX, minY, maxY
def split_polygon(coords: set[tuple[Coordinate, Coordinate]], cut_idx: int, horizontal: bool) -> (set[Coordinate], set[Coordinate]):
"""split the polygon described by coords at cut_idx horizontally or vertically and return both resulting polygons"""
if horizontal:
vertical_edges = {x for x in coords if x[0].x == x[1].x}
intersecting_edges = {x for x in vertical_edges if min(x[0].y, x[1].y) <= cut_idx <= max(x[0].y, x[1].y)}
above = set()
below = set()
for c in coords:
if c in intersecting_edges:
# split into two
# c = ((from_x, from_y), (to_x, to_y))
c_1 = (c[0], Coordinate(c[1].x, cut_idx))
c_2 = (Coordinate(c[0].x, cut_idx), c[1])
if c[0].y <= cut_idx:
above.add(c_1)
below.add(c_2)
else:
above.add(c_2)
below.add(c_1)
else:
if c[0].y <= cut_idx:
above.add(c)
if c[0].y >= cut_idx:
below.add(c)
return above, below
def get_inside_area(coords: set[tuple[Coordinate, Coordinate]]) -> int:
min_x, max_x, min_y, max_y = get_borders(coords)
if len(coords) == 4: # rectangle, hopefully
return (max_x - min_x - 1) * (max_y - min_y - 1)
else:
if max_x - min_x > max_y - min_y:
half_1, half_2 = split_polygon(coords, (max_x - min_x) // 2, horizontal=False)
else:
half_1, half_2 = split_polygon(coords, (max_y - min_y) // 2, horizontal=True)
return get_inside_area(half_1) + get_inside_area(half_2)
class Day(AOCDay):
inputs = [
[
(62, "input18_test"),
(50603, "input18"),
],
[
(952408144115, "input18_test"),
(None, "input18"),
]
]
def parse_input(self, part2: bool = False) -> Iterable[tuple[int, int]]:
for line in self.getInput():
d, s, c = line.split()
if not part2:
yield DIR_TRANS[d], int(s)
else:
yield int(c[-2]), int(c[2:-2], 16)
def get_lagoon_area(self, part2: bool = False) -> int:
start = Coordinate(0, 0)
edges = set()
perimeter = 0
for d, l in self.parse_input(part2):
perimeter += l
end = start + DIRECTIONS[d] * l
edges.add((start, end))
start = end
print(split_polygon(edges, 4, True))
#return get_inside_area(edges) + perimeter
def part1(self) -> Any:
return self.get_lagoon_area()
def part2(self) -> Any:
return ""
if __name__ == '__main__':
day = Day(2023, 18)
day.run(verbose=True)