aoc2023/day10.py
2023-12-10 11:55:15 +01:00

208 lines
5.7 KiB
Python

from collections import deque
from tools.aoc import AOCDay
from tools.coordinate import Coordinate
from tools.grid import Grid
from typing import Any
LOOKUP = {
"|": {
Coordinate(0, 1): {"o_dir": [Coordinate(-1, 0)], "i_dir": [Coordinate(1, 0)], "n_dir": Coordinate(0, 1)},
Coordinate(0, -1): {"o_dir": [Coordinate(1, 0)], "i_dir": [Coordinate(-1, 0)], "n_dir": Coordinate(0, -1)},
},
"-": {
Coordinate(1, 0): {"o_dir": [Coordinate(0, 1)], "i_dir": [Coordinate(0, -1)], "n_dir": Coordinate(1, 0)},
Coordinate(-1, 0): {"o_dir": [Coordinate(0, -1)], "i_dir": [Coordinate(0, 1)], "n_dir": Coordinate(-1, 0)},
},
"7": {
Coordinate(1, 0): {
"o_dir": None,
"i_dir": [Coordinate(0, -1), Coordinate(1, 0)],
"n_dir": Coordinate(0, 1),
},
Coordinate(0, -1): {
"o_dir": [Coordinate(1, 0), Coordinate(0, -1)],
"i_dir": None,
"n_dir": Coordinate(-1, 0),
},
},
"J": {
Coordinate(0, 1): {
"o_dir": None,
"i_dir": [Coordinate(1, 0), Coordinate(0, 1)],
"n_dir": Coordinate(-1, 0),
},
Coordinate(1, 0): {
"o_dir": [Coordinate(1, 0), Coordinate(0, 1)],
"i_dir": None,
"n_dir": Coordinate(0, -1),
},
},
"F": {
Coordinate(0, -1): {
"o_dir": None,
"i_dir": [Coordinate(-1, 0), Coordinate(0, -1)],
"n_dir": Coordinate(1, 0),
},
Coordinate(-1, 0): {
"o_dir": [Coordinate(-1, 0), Coordinate(0, -1)],
"i_dir": None,
"n_dir": Coordinate(0, 1),
},
},
"L": {
Coordinate(-1, 0): {
"o_dir": None,
"i_dir": [Coordinate(-1, 0), Coordinate(0, 1)],
"n_dir": Coordinate(0, -1),
},
Coordinate(0, 1): {
"o_dir": [Coordinate(-1, 0), Coordinate(0, 1)],
"i_dir": None,
"n_dir": Coordinate(1, 0),
},
},
}
def remove_clutter(grid: Grid, start: Coordinate):
pipe_coords = set()
for x in LOOKUP[grid.get(start)]:
dir_ = LOOKUP[grid.get(start)][x]["n_dir"]
break
while start not in pipe_coords:
pipe_coords.add(start)
start += dir_
dir_ = LOOKUP[grid.get(start)][dir_]["n_dir"]
for c in grid.getActiveCells():
if c not in pipe_coords:
grid.set(c, False)
def get_area(grid: Grid, start: Coordinate) -> set[Coordinate]:
if grid.get(start):
return set()
area = set()
q = deque()
q.append(start)
while q:
c = q.popleft()
if c in area:
continue
area.add(c)
for n in c.getNeighbours(includeDiagonal=True):
if grid.get(n):
continue
if grid.isWithinBoundaries(n):
q.append(n)
return area
def walk(grid: Grid, part2: bool = False) -> int:
cur = None
for x in grid.rangeX():
for y in grid.rangeY():
if grid.get(Coordinate(x, y)):
cur = Coordinate(x, y)
break
if cur is not None:
break
assert grid.get(cur) == "F"
pipe_len = 0
direction = Coordinate(0, 1)
outside = get_area(grid, cur + Coordinate(0, -1))
inside = set()
visited = set()
while cur not in visited:
pipe_len += 1
visited.add(cur)
cur += direction
c = grid.get(cur)
if part2:
oc = LOOKUP[c][direction]["o_dir"]
ic = LOOKUP[c][direction]["i_dir"]
if oc is not None:
for doc in oc:
check = cur + doc
if check not in outside and not grid.get(check):
outside |= get_area(grid, check)
if ic is not None:
for dic in ic:
check = cur + dic
if check not in inside and not grid.get(check):
inside |= get_area(grid, check)
direction = LOOKUP[c][direction]["n_dir"]
if part2:
return len(inside)
else:
return pipe_len // 2
class Day(AOCDay):
inputs = [
[
(4, "input10_test1"),
(8, "input10_test2"),
(6864, "input10"),
],
[
(4, "input10_test3"),
(4, "input10_test4"),
(8, "input10_test5"),
(10, "input10_test6"),
(349, "input10"),
],
]
def parse_input(self) -> Grid:
grid = Grid()
start = None
for y, line in enumerate(self.getInput()):
for x, ch in enumerate(line):
if ch == ".":
continue
if ch == "S":
start = Coordinate(x, y)
grid.set(Coordinate(x, y), ch)
up = grid.get(start + Coordinate(0, -1))
down = grid.get(start + Coordinate(0, 1))
left = grid.get(start + Coordinate(-1, 0))
right = grid.get(start + Coordinate(1, 0))
if up in ["7", "|", "F"]:
if left in ["L", "-", "F"]:
grid.set(start, "J")
elif right in ["J", "-", "7"]:
grid.set(start, "L")
else:
grid.set(start, "|")
elif down in ["L", "|", "J"]:
if left in ["L", "-", "F"]:
grid.set(start, "7")
else:
grid.set(start, "F")
else:
grid.set(start, "-")
remove_clutter(grid, start)
return grid
def part1(self) -> Any:
return walk(self.parse_input())
def part2(self) -> Any:
return walk(self.parse_input(), True)
if __name__ == "__main__":
day = Day(2023, 10)
day.run(verbose=True)