From 1640348c40645cb0c10bcec35fd70a26464e71bd Mon Sep 17 00:00:00 2001 From: Stefan Harmuth Date: Mon, 1 Jan 2024 21:35:18 +0100 Subject: [PATCH] Day 18 - Part 2 --- day18.py | 211 ++----------------------------------------- inputs/input18_test2 | 32 +++++++ inputs/input18_test3 | 14 +++ inputs/input18_test4 | 18 ++++ 4 files changed, 74 insertions(+), 201 deletions(-) create mode 100644 inputs/input18_test2 create mode 100644 inputs/input18_test3 create mode 100644 inputs/input18_test4 diff --git a/day18.py b/day18.py index c096f6f..0d236bf 100644 --- a/day18.py +++ b/day18.py @@ -1,214 +1,24 @@ from __future__ import annotations from tools.aoc import AOCDay -from tools.coordinate import Coordinate -from tools.grid import Grid +from tools.coordinate import Coordinate, Polygon 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} -class Edge: - from_coord: Coordinate = None - to_coord: Coordinate = None - - def __init__(self, start: Coordinate, end: Coordinate): - assert start.x == end.x or start.y == end.y, "Invalid Edge" - self.from_coord = min(start, end) - self.to_coord = max(start, end) - - def is_horizontal(self) -> bool: - return self.from_coord.y == self.to_coord.y - - def connects_to(self, other: Edge) -> bool: - return ( - self.from_coord == other.from_coord - or self.from_coord == other.to_coord - or self.to_coord == other.from_coord - or self.to_coord == other.to_coord - ) - - def __hash__(self) -> int: - return hash((self.from_coord, self.to_coord)) - - def __lt__(self, other: Edge) -> bool: - return self.from_coord < other.from_coord - - def __str__(self) -> str: - return f"{self.from_coord} -> {self.to_coord}" - - def __repr__(self) -> str: - return str(self) - - -def get_borders(coords: set[Edge]) -> (int, int, int, int): - """min_x, max_x, min_y, max_y""" - minX = min(c.from_coord.x for c in coords) - maxX = max(c.to_coord.x for c in coords) - minY = min(c.from_coord.y for c in coords) - maxY = max(c.to_coord.y for c in coords) - - return minX, maxX, minY, maxY - - -def split_top_rect(coords: set[Edge]) -> int: - """ - try to find the "highest" rectangle and split it from the rest - return the inner area of the cut-off rectangular - """ - # TODO: handle some other split_edge being somewhere in the middle - # TODO: handle found rectangle being disconnected from the rest - # ################################################################ - # # # # # - # # # # # - # # # # ########### # - # ##### # # ### # - # # # # # - # # ##### ###### - - min_y = min(c.from_coord.y for c in coords) - max_y = max(c.to_coord.y for c in coords) - top_edge = [x for x in coords if x.is_horizontal() and x.from_coord.y == min_y][0] - side_edges = [x for x in coords if not x.is_horizontal() and x.connects_to(top_edge)] - try: - assert len(side_edges) == 2, f"found != 2 side edges: {side_edges} from {top_edge}" - except AssertionError: - print_debug(coords) - exit() - - split_y = max_y - split_edge = None - for c in coords: - if (c.connects_to(side_edges[0]) or c.connects_to(side_edges[1])) and min_y < c.from_coord.y < split_y: - split_y = c.from_coord.y - split_edge = c - - if split_y == max_y: - # last rect - min_x, max_x, min_y, max_y = get_borders(coords) - for x in coords.copy(): - coords.remove(x) # empty coords in-place - return abs(max_x - min_x - 1) * abs(max_y - min_y - 1) - - assert split_edge is not None, f"could not find split_edge {top_edge=}, {side_edges=}" - - # print(f"{top_edge=}, {split_edge=}") - # print(f"{side_edges=}") - - top_min_x, top_max_x = top_edge.from_coord.x, top_edge.to_coord.x - split_min_x, split_max_x = split_edge.from_coord.x, split_edge.to_coord.x - side_edge_max_y_1 = side_edges[0].to_coord.y - side_edge_max_y_2 = side_edges[1].to_coord.y - - # here we should know enough to remove all edges involved - coords.remove(top_edge) - coords.remove(split_edge) - for s in side_edges: - coords.remove(s) - - # now to connect the open ends - - # create new side edge - if side_edge_max_y_1 > side_edge_max_y_2: - old_edge = side_edges[0] - coords.add( - Edge(Coordinate(old_edge.from_coord.x, split_y), Coordinate(old_edge.from_coord.x, side_edge_max_y_1)) - ) - elif side_edge_max_y_1 < side_edge_max_y_2: - old_edge = side_edges[1] - coords.add( - Edge(Coordinate(old_edge.from_coord.x, split_y), Coordinate(old_edge.from_coord.x, side_edge_max_y_2)) - ) - - area = abs(top_max_x - top_min_x - 1) * abs(split_y - min_y) - if side_edge_max_y_1 == side_edge_max_y_2: - print("edges to both sides") - # TODO: find the other corresponding horizontal edge, remove both edges and connect with one covering both ends - split_edges = {x for x in coords if x.is_horizontal() and x.from_coord.y == split_y} - other_split_edge = None - for edge in split_edges: - if edge != split_edge and (edge.connects_to(side_edges[0]) or edge.connects_to(side_edges[1])): - other_split_edge = edge - break - print(f"found split edge: {split_edge}") - print(f"other split edge: {other_split_edge}") - assert other_split_edge is not None, "could not find corresponding split edge" - coords.remove(other_split_edge) - if split_edge.from_coord.x < other_split_edge.from_coord.x: - left_edge = split_edge - right_edge = other_split_edge - else: - left_edge = other_split_edge - right_edge = split_edge - - if left_edge.from_coord.x < top_edge.from_coord.x: # left_edge goes to the left - left_x = left_edge.from_coord.x - else: # left edge goes inside - left_x = left_edge.to_coord.x - area -= abs(left_edge.to_coord.x - top_edge.from_coord.x) - - if right_edge.to_coord.x > top_edge.to_coord.x: - right_x = right_edge.to_coord.x - else: # right edge goes inside - right_x = right_edge.from_coord.x - area -= abs(top_edge.to_coord.x - right_edge.from_coord.x) - - # TODO: create new connecting edge - coords.add(Edge(Coordinate(left_x, split_y), Coordinate(right_x, split_y))) - elif top_min_x == split_max_x: # edge goes to the left - print("edges goes to the left") - coords.add(Edge(Coordinate(split_min_x, split_y), Coordinate(top_max_x, split_y))) - elif top_max_x == split_min_x: # edge goes to the right - print("edges goes to the right") - coords.add(Edge(Coordinate(top_min_x, split_y), Coordinate(split_max_x, split_y))) - elif top_min_x == split_min_x: # edge goes inside from the left - print("edges goes inside from the left") - area -= abs(split_max_x - split_min_x) - coords.add(Edge(Coordinate(split_max_x, split_y), Coordinate(top_max_x, split_y))) - elif top_max_x == split_max_x: # edge goes inside from the right - print("edges goes inside from the right") - area -= abs(split_max_x - split_min_x) - coords.add(Edge(Coordinate(top_min_x, split_y), Coordinate(split_min_x, split_y))) - else: - assert False, "where does my split_edge go?!" - - return area - - -def print_debug(coords: set[Edge]): - grid = Grid() - for coord in coords: - try: - for c in coord.from_coord.getLineTo(coord.to_coord): - grid.set(c) - except ZeroDivisionError: - print("ZDE:", coords) - exit() - - grid.print() - - -def get_inside_area(coords: set[Edge]) -> int: - area = 0 - while coords: - print_debug(coords) - f = split_top_rect(coords) - area += f - print(f"area removed: {f}, new {area=}") - print("=" * 50) - - return area - - class Day(AOCDay): inputs = [ [ (62, "input18_test"), + (237, "input18_test2"), + (227, "input18_test3"), + (54, "input18_test4"), (50603, "input18"), ], [ (952408144115, "input18_test"), - (None, "input18"), + (96556251590677, "input18"), ], ] @@ -222,23 +32,22 @@ class Day(AOCDay): def get_lagoon_area(self, part2: bool = False) -> int: start = Coordinate(0, 0) - edges = set() + points = [] perimeter = 0 for d, l in self.parse_input(part2): perimeter += l end = start + DIRECTIONS[d] * l - edges.add(Edge(start, end)) + points.append(end) start = end - print_debug(edges) - return get_inside_area(edges) + perimeter + p = Polygon(points) + a = abs(p.get_area()) + return int(p.get_area()) + perimeter // 2 + 1 def part1(self) -> Any: return self.get_lagoon_area() def part2(self) -> Any: - if not self.is_test(): - return "" return self.get_lagoon_area(part2=True) diff --git a/inputs/input18_test2 b/inputs/input18_test2 new file mode 100644 index 0000000..78c10e1 --- /dev/null +++ b/inputs/input18_test2 @@ -0,0 +1,32 @@ +R 10 (#70c710) +D 2 (#0dc571) +L 4 (#0dc571) +D 3 (#0dc571) +R 7 (#0dc571) +U 2 (#0dc571) +R 3 (#0dc571) +D 5 (#0dc571) +L 6 (#0dc571) +D 3 (#0dc571) +L 2 (#5713f0) +U 4 (#d2c081) +L 2 (#59c680) +D 6 (#411b91) +L 4 (#8ceee2) +D 3 (#caa173) +L 5 (#caa173) +U 5 (#caa173) +R 3 (#caa173) +U 2 (#caa173) +L 5 (#caa173) +U 3 (#caa173) +R 1 (#caa173) +U 2 (#caa173) +L 2 (#caa173) +U 4 (#caa173) +R 2 (#caa173) +D 2 (#caa173) +R 2 (#caa173) +D 4 (#caa173) +R 2 (#caa173) +U 6 (#caa173) \ No newline at end of file diff --git a/inputs/input18_test3 b/inputs/input18_test3 new file mode 100644 index 0000000..3bf36d9 --- /dev/null +++ b/inputs/input18_test3 @@ -0,0 +1,14 @@ +R 7 (#000000) +D 2 (#000000) +R 9 (#000000) +D 9 (#000000) +R 5 (#000000) +D 3 (#000000) +L 9 (#000000) +U 9 (#000000) +L 3 (#000000) +D 9 (#000000) +L 7 (#000000) +U 6 (#000000) +L 2 (#000000) +U 8 (#000000) diff --git a/inputs/input18_test4 b/inputs/input18_test4 new file mode 100644 index 0000000..1aeeb3a --- /dev/null +++ b/inputs/input18_test4 @@ -0,0 +1,18 @@ +L 6 (#000000) +D 2 (#000000) +L 3 (#000000) +D 2 (#000000) +R 1 (#000000) +D 2 (#000000) +R 2 (#000000) +U 1 (#000000) +R 2 (#000000) +U 1 (#000000) +R 2 (#000000) +U 1 (#000000) +L 2 (#000000) +U 1 (#000000) +R 3 (#000000) +D 3 (#000000) +R 1 (#000000) +U 5 (#000000)