2022: d22: ex1: add solution
This commit is contained in:
parent
b819e62da4
commit
eeaf6956e5
133
2022/d22/ex1/ex1.py
Executable file
133
2022/d22/ex1/ex1.py
Executable file
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import enum
|
||||
import itertools
|
||||
import sys
|
||||
from collections.abc import Iterable, Iterator
|
||||
from typing import NamedTuple, TypeVar, Union
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def take(n: int, iterable: Iterable[T]) -> Iterator[T]:
|
||||
return itertools.islice(iterable, n)
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return NotImplemented
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, Point):
|
||||
return NotImplemented
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
|
||||
|
||||
class Tile(str, enum.Enum):
|
||||
AIR = "."
|
||||
WALL = "#"
|
||||
|
||||
|
||||
class Direction(enum.IntEnum):
|
||||
EAST = 0
|
||||
SOUTH = 1
|
||||
WEST = 2
|
||||
NORTH = 3
|
||||
|
||||
def turn(self, rot: "Rotation") -> "Direction":
|
||||
if rot == Rotation.LEFT:
|
||||
return Direction((self - 1 + 4) % 4)
|
||||
if rot == Rotation.RIGHT:
|
||||
return Direction((self + 1) % 4)
|
||||
assert False # Sanity check
|
||||
|
||||
def to_delta(self) -> Point:
|
||||
match self:
|
||||
case Direction.NORTH:
|
||||
return Point(-1, 0)
|
||||
case Direction.SOUTH:
|
||||
return Point(1, 0)
|
||||
case Direction.EAST:
|
||||
return Point(0, 1)
|
||||
case Direction.WEST:
|
||||
return Point(0, -1)
|
||||
|
||||
|
||||
class Rotation(str, enum.Enum):
|
||||
LEFT = "L"
|
||||
RIGHT = "R"
|
||||
|
||||
|
||||
Map = dict[Point, Tile]
|
||||
|
||||
|
||||
def solve(input: list[str]) -> int:
|
||||
def parse_map(input: list[str]) -> tuple[Point, Map]:
|
||||
res: Map = {}
|
||||
|
||||
for i, line in enumerate(input, start=1):
|
||||
for j, c in enumerate(line, start=1):
|
||||
if c == " ":
|
||||
continue
|
||||
res[Point(i, j)] = Tile(c)
|
||||
|
||||
return min(p for p in res.keys()), res
|
||||
|
||||
def parse_instruction(input: str) -> list[Union[Rotation, int]]:
|
||||
res: list[Union[Rotation, int]] = []
|
||||
i = 0
|
||||
while i < len(input):
|
||||
# Parse direction
|
||||
if input[i] in list(Rotation):
|
||||
res.append(Rotation(input[i]))
|
||||
i += 1
|
||||
continue
|
||||
# Parse int
|
||||
j = i + 1
|
||||
while j < len(input) and input[j] not in list(Rotation):
|
||||
j += 1
|
||||
res.append(int(input[i:j]))
|
||||
i = j
|
||||
|
||||
return res
|
||||
|
||||
def points_along(start: Point, map: Map, dir: Direction) -> Iterator[Point]:
|
||||
delta = dir.to_delta()
|
||||
while True:
|
||||
start = start + delta
|
||||
# Wrap around if about to go out-of-bounds
|
||||
if start not in map:
|
||||
while (new_start := start - delta) in map:
|
||||
start = new_start
|
||||
yield start
|
||||
|
||||
assert input[-2] == "" # Sanity check
|
||||
|
||||
facing = Direction.EAST
|
||||
start, map = parse_map(input[:-2])
|
||||
instructions = parse_instruction(input[-1])
|
||||
|
||||
for instr in instructions:
|
||||
if isinstance(instr, Rotation):
|
||||
facing = facing.turn(instr)
|
||||
continue
|
||||
for p in take(instr, points_along(start, map, facing)):
|
||||
if map[p] == Tile.WALL:
|
||||
break
|
||||
start = p
|
||||
|
||||
return 1000 * start.x + 4 * start.y + facing
|
||||
|
||||
|
||||
def main() -> None:
|
||||
input = sys.stdin.read().splitlines()
|
||||
print(solve(input))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue