advent-of-code/2023/d18/ex2/ex2.py

98 lines
2.4 KiB
Python
Raw Normal View History

2023-12-18 09:03:12 +01:00
#!/usr/bin/env python
import itertools
import sys
from enum import StrEnum
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
class Direction(StrEnum):
UP = "U"
DOWN = "D"
LEFT = "L"
RIGHT = "R"
def apply(self, pos: Point, n: int = 1) -> Point:
DIRECTIONS = {
"U": Point(-1, 0),
"D": Point(1, 0),
"L": Point(0, -1),
"R": Point(0, 1),
}
dx, dy = DIRECTIONS[self.value]
return Point(pos.x + dx * n, pos.y + dy * n)
DigPlanStep = tuple[Direction, int]
DigPlan = list[DigPlanStep]
def solve(input: list[str]) -> int:
def parse_line(line: str) -> DigPlanStep:
_, _, color = line.split()
color = color[2:-1]
n = color[:-1]
dir = {
"0": "R",
"1": "D",
"2": "L",
"3": "U",
}[color[-1]]
return Direction(dir), int(n, base=16)
def parse(input: list[str]) -> DigPlan:
return list(map(parse_line, input))
def dig_trench(plan: DigPlan) -> list[Point]:
points = [Point(0, 0)]
for direction, n in plan:
points.append(direction.apply(points[-1], n))
# The trench should loop back to the start, make sure we don't count it twice
if points[-1] == Point(0, 0):
del points[-1]
return points
def lagoon_volume(trench: list[Point]) -> int:
def shoelace_area(points: list[Point]) -> int:
# Must be integer because pipes follow the grid, and can't cut squares in half
return abs(
sum(
(points[i - 1].x * points[i].y) - (points[i].x * points[i - 1].y)
for i in range(len(points))
)
// 2
)
def perimeter(points: list[Point]) -> int:
res = 0
for p, n in itertools.pairwise(itertools.chain(points, [points[0]])):
res += abs(n.x - p.x) + abs(n.y - p.y)
return res
area = shoelace_area(trench)
trench_points = perimeter(trench)
interior_points = area - trench_points // 2 + 1
return interior_points + trench_points
plan = parse(input)
trench = dig_trench(plan)
return lagoon_volume(list(trench))
def main() -> None:
input = sys.stdin.read().splitlines()
print(solve(input))
if __name__ == "__main__":
main()