generated from public/aoc_template
Day 18 - a maybe solution, with some "edge"cases still to handle
This commit is contained in:
parent
8b888890b5
commit
33154a92ff
242
day18.py
242
day18.py
@ -1,59 +1,204 @@
|
|||||||
|
from __future__ import annotations
|
||||||
from tools.aoc import AOCDay
|
from tools.aoc import AOCDay
|
||||||
from tools.coordinate import Coordinate
|
from tools.coordinate import Coordinate
|
||||||
|
from tools.grid import Grid
|
||||||
from typing import Any, Iterable
|
from typing import Any, Iterable
|
||||||
|
|
||||||
|
|
||||||
DIRECTIONS = [Coordinate(1, 0), Coordinate(0, 1), Coordinate(-1, 0), Coordinate(0, -1)]
|
DIRECTIONS = [Coordinate(1, 0), Coordinate(0, 1), Coordinate(-1, 0), Coordinate(0, -1)]
|
||||||
DIR_TRANS = {'R': 0, 'D': 1, 'L': 2, 'U': 3}
|
DIR_TRANS = {"R": 0, "D": 1, "L": 2, "U": 3}
|
||||||
|
|
||||||
|
|
||||||
def get_borders(coords: set[tuple[Coordinate, Coordinate]]) -> (int, int, int, int):
|
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"""
|
"""min_x, max_x, min_y, max_y"""
|
||||||
minX = list(sorted(coords))[0][0][0]
|
minX = min(c.from_coord.x for c in coords)
|
||||||
minY = list(sorted(coords, key=lambda d: (d[0][1], d[0][0])))[0][0][1]
|
maxX = max(c.to_coord.x for c in coords)
|
||||||
maxX = list(sorted(coords, reverse=True))[0][0][0]
|
minY = min(c.from_coord.y for c in coords)
|
||||||
maxY = list(sorted(coords, key=lambda d: (d[0][1], d[0][0]), reverse=True))[0][0][1]
|
maxY = max(c.to_coord.y for c in coords)
|
||||||
|
|
||||||
return minX, maxX, minY, maxY
|
return minX, maxX, minY, maxY
|
||||||
|
|
||||||
|
|
||||||
def split_polygon(coords: set[tuple[Coordinate, Coordinate]], cut_idx: int, horizontal: bool) -> (set[Coordinate], set[Coordinate]):
|
def split_top_rect(coords: set[Edge]) -> int:
|
||||||
"""split the polygon described by coords at cut_idx horizontally or vertically and return both resulting polygons"""
|
"""
|
||||||
if horizontal:
|
try to find the "highest" rectangle and split it from the rest
|
||||||
vertical_edges = {x for x in coords if x[0].x == x[1].x}
|
return the inner area of the cut-off rectangular
|
||||||
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()
|
# TODO: handle some other split_edge being somewhere in the middle
|
||||||
below = set()
|
# TODO: handle found rectangle being disconnected from the rest
|
||||||
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
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
def get_inside_area(coords: set[tuple[Coordinate, Coordinate]]) -> int:
|
split_y = max_y
|
||||||
min_x, max_x, min_y, max_y = get_borders(coords)
|
split_edge = None
|
||||||
if len(coords) == 4: # rectangle, hopefully
|
for c in coords:
|
||||||
return (max_x - min_x - 1) * (max_y - min_y - 1)
|
if (c.connects_to(side_edges[0]) or c.connects_to(side_edges[1])) and min_y < c.from_coord.y < split_y:
|
||||||
else:
|
split_y = c.from_coord.y
|
||||||
if max_x - min_x > max_y - min_y:
|
split_edge = c
|
||||||
half_1, half_2 = split_polygon(coords, (max_x - min_x) // 2, horizontal=False)
|
|
||||||
|
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:
|
else:
|
||||||
half_1, half_2 = split_polygon(coords, (max_y - min_y) // 2, horizontal=True)
|
left_edge = other_split_edge
|
||||||
return get_inside_area(half_1) + get_inside_area(half_2)
|
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):
|
class Day(AOCDay):
|
||||||
inputs = [
|
inputs = [
|
||||||
@ -64,7 +209,7 @@ class Day(AOCDay):
|
|||||||
[
|
[
|
||||||
(952408144115, "input18_test"),
|
(952408144115, "input18_test"),
|
||||||
(None, "input18"),
|
(None, "input18"),
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse_input(self, part2: bool = False) -> Iterable[tuple[int, int]]:
|
def parse_input(self, part2: bool = False) -> Iterable[tuple[int, int]]:
|
||||||
@ -82,20 +227,21 @@ class Day(AOCDay):
|
|||||||
for d, l in self.parse_input(part2):
|
for d, l in self.parse_input(part2):
|
||||||
perimeter += l
|
perimeter += l
|
||||||
end = start + DIRECTIONS[d] * l
|
end = start + DIRECTIONS[d] * l
|
||||||
edges.add((start, end))
|
edges.add(Edge(start, end))
|
||||||
start = end
|
start = end
|
||||||
|
|
||||||
print(split_polygon(edges, 4, True))
|
print_debug(edges)
|
||||||
|
return get_inside_area(edges) + perimeter
|
||||||
#return get_inside_area(edges) + perimeter
|
|
||||||
|
|
||||||
def part1(self) -> Any:
|
def part1(self) -> Any:
|
||||||
return self.get_lagoon_area()
|
return self.get_lagoon_area()
|
||||||
|
|
||||||
def part2(self) -> Any:
|
def part2(self) -> Any:
|
||||||
return ""
|
if not self.is_test():
|
||||||
|
return ""
|
||||||
|
return self.get_lagoon_area(part2=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
day = Day(2023, 18)
|
day = Day(2023, 18)
|
||||||
day.run(verbose=True)
|
day.run(verbose=True)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user