2024: d20: ex2: add solution
This commit is contained in:
parent
626bc0f2af
commit
2d196f9c52
107
2024/d20/ex2/ex2.py
Executable file
107
2024/d20/ex2/ex2.py
Executable file
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
from collections.abc import Iterator
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def neighbours(self) -> Iterator["Point"]:
|
||||
for dx, dy in (
|
||||
(-1, 0),
|
||||
(1, 0),
|
||||
(0, -1),
|
||||
(0, 1),
|
||||
):
|
||||
yield Point(self.x + dx, self.y + dy)
|
||||
|
||||
|
||||
class ParsedMap(NamedTuple):
|
||||
start: Point
|
||||
end: Point
|
||||
tracks: set[Point]
|
||||
|
||||
|
||||
MIN_SAVE = 100
|
||||
|
||||
|
||||
def solve(input: str) -> int:
|
||||
def parse(input: list[str]) -> ParsedMap:
|
||||
start: Point | None = None
|
||||
end: Point | None = None
|
||||
tracks: set[Point] = set()
|
||||
|
||||
for x, line in enumerate(input):
|
||||
for y, c in enumerate(line):
|
||||
if c == "#":
|
||||
continue
|
||||
p = Point(x, y)
|
||||
if c == "S":
|
||||
start = p
|
||||
elif c == "E":
|
||||
end = p
|
||||
tracks.add(p)
|
||||
|
||||
assert start is not None and end is not None # Sanity check
|
||||
return ParsedMap(start, end, tracks)
|
||||
|
||||
def flood_distance(start: Point, points: set[Point]) -> dict[Point, int]:
|
||||
res = {start: 0}
|
||||
queue = {start}
|
||||
|
||||
while queue:
|
||||
p = queue.pop()
|
||||
dist = res[p]
|
||||
for n in p.neighbours():
|
||||
if n in res:
|
||||
continue
|
||||
if n not in points:
|
||||
continue
|
||||
res[n] = dist + 1
|
||||
queue.add(n)
|
||||
|
||||
return res
|
||||
|
||||
def dist(a: Point, b: Point) -> int:
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def disk(p: Point, radius: int) -> Iterator[Point]:
|
||||
for dx, dy in itertools.product(range(-radius, radius + 1), repeat=2):
|
||||
n = Point(p.x + dx, p.y + dy)
|
||||
if dist(p, n) > radius:
|
||||
continue
|
||||
yield n
|
||||
|
||||
def find_cheats(start: Point, end: Point, tracks: set[Point]) -> int:
|
||||
start_dist = flood_distance(start, tracks)
|
||||
end_dist = flood_distance(end, tracks)
|
||||
|
||||
assert start_dist[end] == end_dist[start]
|
||||
fastest = start_dist[end]
|
||||
|
||||
res = 0
|
||||
for a in tracks:
|
||||
for b in disk(a, 20):
|
||||
if b not in tracks:
|
||||
continue
|
||||
time = start_dist[a] + dist(a, b) + end_dist[b]
|
||||
if (fastest - time) < MIN_SAVE:
|
||||
continue
|
||||
res += 1
|
||||
return res
|
||||
|
||||
start, end, tracks = parse(input.splitlines())
|
||||
return find_cheats(start, end, tracks)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
input = sys.stdin.read()
|
||||
print(solve(input))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue