2023/day10/common.py
Fedaya 64a270ee39 Day 10 completed
completed with the help of shapely python module

other minors changes in day5
2023-12-10 17:34:02 +01:00

174 lines
5.4 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from collections import defaultdict
import logging
from typing import NamedTuple, TypeGuard
from enum import Enum
import sys
class Point(NamedTuple):
x: int
y: int
c: str
def add_delta(self, delta: PointDelta, labyrinth: Labyrinth) -> Point | None:
if (0 <= self.x + delta.x < len(labyrinth[0])) and (
0 <= self.y + delta.y < len(labyrinth)
):
return labyrinth[self.y + delta.y][self.x + delta.x]
return None
class PointDelta(NamedTuple):
x: int
y: int
class Deltas(Enum):
UP = PointDelta(0, -1)
DOWN = PointDelta(0, 1)
LEFT = PointDelta(-1, 0)
RIGHT = PointDelta(1, 0)
NONE = PointDelta(0, 0)
NO_MOVEMENT = (Deltas.NONE, Deltas.NONE)
PIPES = defaultdict(
lambda: NO_MOVEMENT,
[
("|", (Deltas.UP, Deltas.DOWN)),
("-", (Deltas.LEFT, Deltas.RIGHT)),
("L", (Deltas.UP, Deltas.RIGHT)),
("J", (Deltas.UP, Deltas.LEFT)),
("7", (Deltas.DOWN, Deltas.LEFT)),
("F", (Deltas.DOWN, Deltas.RIGHT)),
],
)
@dataclass(init=False)
class Labyrinth:
_data: tuple[tuple[Point, ...], ...] # stored as [Y][X]
start: Point
first_points: tuple[Point, ...]
def __init__(
self,
data: tuple[tuple[Point, ...], ...],
start: Point,
first_points: tuple[Point, ...] | None = None,
):
self._data = data
self.start = start
self.first_points = (
self._find_first_points() if first_points is None else first_points
)
logging.debug(("start", start))
logging.debug(("first_points", self.first_points))
def __getitem__(self, key: int) -> tuple[Point, ...]:
return self._data[key]
def __len__(self):
return len(self._data)
def __iter__(self):
return iter(self._data)
def _find_first_points(self) -> tuple[Point, ...]:
up = self.start.add_delta(Deltas.UP.value, self)
up = up if up is not None and Deltas.DOWN in PIPES[up.c] else None
down = self.start.add_delta(Deltas.DOWN.value, self)
down = down if down is not None and Deltas.UP in PIPES[down.c] else None
left = self.start.add_delta(Deltas.LEFT.value, self)
left = left if left is not None and Deltas.RIGHT in PIPES[left.c] else None
right = self.start.add_delta(Deltas.RIGHT.value, self)
right = right if right is not None and Deltas.LEFT in PIPES[right.c] else None
def point_is_not_none(n: Point | None) -> TypeGuard[Point]: # makes mypy happy
return n is not None
return tuple(filter(point_is_not_none, [up, down, left, right]))
def get_labyrinth(self):
return self._data
def get_path(self) -> list[Point]:
path = [self.start]
previous_point = self.start
current_point = self.first_points[0]
while current_point != self.start:
path.append(current_point)
new_point = movement(current_point, previous_point, self)
previous_point = current_point
current_point = new_point
logging.debug(current_point)
path.append(current_point)
return path
def get_empty_points(self) -> list[Point]:
empty_points = []
for line in self._data:
for point in line:
if point.c == ".":
empty_points.append(point)
return empty_points
def clean(self) -> Labyrinth:
path = self.get_path()
new_data = []
for y, line in enumerate(self._data):
new_line = []
for x, point in enumerate(line):
if point not in path:
new_line.append(Point(x, y, "."))
else:
new_line.append(point)
new_data.append(tuple(new_line))
return Labyrinth(new_data, self.start, self.first_points)
def show(self, descriptor=sys.stdout) -> None:
for line in self._data:
linestr = "".join(map(lambda p: p.c, line)) + "\n"
descriptor.write(linestr)
def movement(
current_point: Point, previous_point: Point, labyrinth: Labyrinth
) -> Point:
movement_type = PIPES[current_point.c]
if movement_type == NO_MOVEMENT:
return current_point
movement_value = tuple(x.value for x in movement_type)
diff = PointDelta(
previous_point.x - current_point.x, previous_point.y - current_point.y
)
if diff not in movement_value:
return current_point
idx = 0 if movement_value.index(diff) == 1 else 1
return labyrinth[current_point.y + movement_value[idx].y][
current_point.x + movement_value[idx].x
]
def parse(input_file: str) -> Labyrinth:
logging.debug(f"parsing {input_file}")
array = []
start_point = Point(-1, -1, "")
line_no = 0
with open(input_file, "r", encoding="UTF-8") as input_handle:
while line := input_handle.readline().rstrip("\n").rstrip():
ar_line = []
for col_no, char in enumerate(line):
new_point = Point(col_no, line_no, char)
logging.debug(f"new point: {new_point}")
ar_line.append(new_point)
if char == "S":
start_point = new_point
array.append(tuple(ar_line))
line_no += 1
return Labyrinth(data=tuple(array), start=start_point)