generated from public/aoc_template
Day 18 - Part 2
This commit is contained in:
parent
b053e244e4
commit
1640348c40
211
day18.py
211
day18.py
@ -1,214 +1,24 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from tools.aoc import AOCDay
|
from tools.aoc import AOCDay
|
||||||
from tools.coordinate import Coordinate
|
from tools.coordinate import Coordinate, Polygon
|
||||||
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}
|
||||||
|
|
||||||
|
|
||||||
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):
|
class Day(AOCDay):
|
||||||
inputs = [
|
inputs = [
|
||||||
[
|
[
|
||||||
(62, "input18_test"),
|
(62, "input18_test"),
|
||||||
|
(237, "input18_test2"),
|
||||||
|
(227, "input18_test3"),
|
||||||
|
(54, "input18_test4"),
|
||||||
(50603, "input18"),
|
(50603, "input18"),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
(952408144115, "input18_test"),
|
(952408144115, "input18_test"),
|
||||||
(None, "input18"),
|
(96556251590677, "input18"),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -222,23 +32,22 @@ class Day(AOCDay):
|
|||||||
|
|
||||||
def get_lagoon_area(self, part2: bool = False) -> int:
|
def get_lagoon_area(self, part2: bool = False) -> int:
|
||||||
start = Coordinate(0, 0)
|
start = Coordinate(0, 0)
|
||||||
edges = set()
|
points = []
|
||||||
perimeter = 0
|
perimeter = 0
|
||||||
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(Edge(start, end))
|
points.append(end)
|
||||||
start = end
|
start = end
|
||||||
|
|
||||||
print_debug(edges)
|
p = Polygon(points)
|
||||||
return get_inside_area(edges) + perimeter
|
a = abs(p.get_area())
|
||||||
|
return int(p.get_area()) + perimeter // 2 + 1
|
||||||
|
|
||||||
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:
|
||||||
if not self.is_test():
|
|
||||||
return ""
|
|
||||||
return self.get_lagoon_area(part2=True)
|
return self.get_lagoon_area(part2=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
32
inputs/input18_test2
Normal file
32
inputs/input18_test2
Normal file
@ -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)
|
||||||
14
inputs/input18_test3
Normal file
14
inputs/input18_test3
Normal file
@ -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)
|
||||||
18
inputs/input18_test4
Normal file
18
inputs/input18_test4
Normal file
@ -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)
|
||||||
Loading…
Reference in New Issue
Block a user