aoc2023/aoc/day10.py
2023-12-11 02:07:26 +01:00

146 lines
4.5 KiB
Python

from dataclasses import dataclass
from typing import TypeAlias
from .aoc import Aoc2, AocSameParser
Position: TypeAlias = tuple[int, int]
Graph: TypeAlias = dict[Position, list[Position]]
@dataclass
class PipeDiagram:
raw_input: list[str]
start_pos: Position
start_shape: str
reachable_nodes: list[Position]
class Day10(AocSameParser[PipeDiagram], Aoc2[PipeDiagram, PipeDiagram]):
@staticmethod
def position_plus(a: Position, b: Position) -> Position:
return (a[0] + b[0], a[1] + b[1])
@staticmethod
def find_start(inpt: list[str]) -> Position:
for y, line in enumerate(inpt):
try:
return line.index("S"), y
except ValueError:
pass
raise ValueError("S not found")
@staticmethod
def connects(shape: str) -> list[Position]:
match shape:
case "|":
return [(0, 1), (0, -1)]
case "-":
return [(1, 0), (-1, 0)]
case "F":
return [(0, 1), (1, 0)]
case "L":
return [(0, -1), (1, 0)]
case "J":
return [(0, -1), (-1, 0)]
case "7":
return [(0, 1), (-1, 0)]
case _:
return []
@staticmethod
def shape_of_start(pos: Position, inpt: list[str]) -> str:
possibilities = [
inpt[npos[1]][npos[0]]
for npos in (
Day10.position_plus(pos, next)
for next in ((-1, 0), (1, 0), (0, 1), (0, -1))
)
] # Left, Right, Down, Up
possibles_shapes = {"|", "-", "F", "L", "J", "7"}
if possibilities[0] not in "FL-":
possibles_shapes = possibles_shapes.difference({"-", "7", "J"})
if possibilities[1] not in "J7-":
possibles_shapes = possibles_shapes.difference({"-", "F", "L"})
if possibilities[2] not in "J|L":
possibles_shapes = possibles_shapes.difference({"|", "F", "7"})
if possibilities[3] not in "7|F":
possibles_shapes = possibles_shapes.difference({"|", "J", "L"})
if len(possibles_shapes) != 1:
raise RuntimeError("Malformed Pipes")
return list(possibles_shapes)[0]
@staticmethod
def reach(
start_pos: Position, shape_of_start: str, inpt: list[str]
) -> list[Position]:
reach = [start_pos]
last_pos = start_pos
current_pos = Day10.position_plus(start_pos, Day10.connects(shape_of_start)[0])
while current_pos != start_pos:
reach.append(current_pos)
shape = inpt[current_pos[1]][current_pos[0]]
next_pos = list(
filter(
lambda p: p != last_pos,
(
Day10.position_plus(current_pos, p)
for p in Day10.connects(shape)
),
)
)
last_pos = current_pos
current_pos = next_pos[0]
return reach
def parseinput(self, inpt: str) -> PipeDiagram:
lines = inpt.splitlines()
start_pos = Day10.find_start(lines)
start_shape = Day10.shape_of_start(start_pos, lines)
positions = Day10.reach(start_pos, start_shape, inpt.splitlines())
return PipeDiagram(lines, start_pos, start_shape, positions)
def part1(self, inpt: PipeDiagram) -> int:
return len(inpt.reachable_nodes) // 2
def part2(self, inpt: PipeDiagram) -> int:
r = set(inpt.reachable_nodes)
inside = 0
for y, line in enumerate(inpt.raw_input):
last_char = None
accum = 0
for x, char in enumerate(line):
if (x, y) not in r:
if accum % 2 != 0:
inside += 1
continue
if char == "|":
accum += 1
elif char == "S":
char = inpt.start_shape
if char not in "FJ7L":
continue
if last_char is None:
last_char = char
else:
match last_char, char:
case "F", "J":
accum += 1
case "L", "7":
accum += 1
last_char = char
return inside
def main() -> None:
day10 = Day10(2023, 10, example_code_nr1=6, example_code_nr2=-10)
day10.run()
if __name__ == "__main__":
main()