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)