generated from public/aoc_template
248 lines
8.7 KiB
Python
248 lines
8.7 KiB
Python
from __future__ import annotations
|
|
from tools.aoc import AOCDay
|
|
from tools.coordinate import Coordinate
|
|
from tools.grid import Grid
|
|
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"),
|
|
(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(Edge(start, end))
|
|
start = end
|
|
|
|
print_debug(edges)
|
|
return get_inside_area(edges) + perimeter
|
|
|
|
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)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
day = Day(2023, 18)
|
|
day.run(verbose=True)
|