146 lines
4.5 KiB
Python
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()
|