From 395463dc4a59ce79fd2748306e343db8b5d26aca Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Fri, 20 Dec 2024 09:46:53 +0100 Subject: [PATCH] Implement 2024 day 20 --- 2024/src/aoc/days/day20.py | 82 ++++++++++++++++++++++++++++++++++++++ 2024/tests/samples/20.txt | 15 +++++++ 2024/tests/test_day20.py | 11 +++++ 3 files changed, 108 insertions(+) create mode 100644 2024/src/aoc/days/day20.py create mode 100644 2024/tests/samples/20.txt create mode 100644 2024/tests/test_day20.py diff --git a/2024/src/aoc/days/day20.py b/2024/src/aoc/days/day20.py new file mode 100644 index 0000000..37f68c9 --- /dev/null +++ b/2024/src/aoc/days/day20.py @@ -0,0 +1,82 @@ +import itertools + +import numpy + +from . import SeparateRunner + +DIRECTIONS = [ + (-1, 0), + (1, 0), + (0, -1), + (0, 1), +] + +CHEATS = [ + (-2, 0), + (2, 0), + (0, -2), + (0, 2), +] + + +def parse_path(input: str) -> dict[tuple[int, int], int]: + grid = numpy.array(list(map(list, input.strip().split("\n")))) + + ys, xs = numpy.nonzero(grid == "S") + sx, sy = int(xs[0]), int(ys[0]) + + nx, ny = sx, sy + + path = { + (sx, sy): 0, + } + + while grid[ny, nx] != "E": + x, y = nx, ny + + for dx, dy in DIRECTIONS: + if grid[y + dy, x + dx] == "#" or (x + dx, y + dy) in path: + continue + nx = x + dx + ny = y + dy + break + + path[nx, ny] = len(path) + return path + + +def get_savings(a: tuple[tuple[int, int], int], b: tuple[tuple[int, int], int]) -> int: + (ax, ay), ad = a + (bx, by), bd = b + + dist = abs(bx - ax) + abs(by - ay) + if dist <= 20: + return bd - ad - dist + else: + return 0 + + +class DayRunner(SeparateRunner): + @classmethod + def part1(cls, input: str, limit: int = 100) -> int: + path = parse_path(input) + + total = 0 + + for (px, py), dist in path.items(): + for dx, dy in CHEATS: + if (other := path.get((px + dx, py + dy))) is not None: + savings = dist - other - 2 + if savings >= limit: + total += 1 + + return total + + @classmethod + def part2(cls, input: str, limit: int = 100) -> int: + path = parse_path(input) + + return sum( + get_savings(a, b) >= limit + for a, b in itertools.combinations(path.items(), 2) + ) diff --git a/2024/tests/samples/20.txt b/2024/tests/samples/20.txt new file mode 100644 index 0000000..c097dae --- /dev/null +++ b/2024/tests/samples/20.txt @@ -0,0 +1,15 @@ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +############### diff --git a/2024/tests/test_day20.py b/2024/tests/test_day20.py new file mode 100644 index 0000000..85aaef5 --- /dev/null +++ b/2024/tests/test_day20.py @@ -0,0 +1,11 @@ +from aoc.days.day20 import DayRunner + +from . import get_data + + +def test_sample_part1() -> None: + assert DayRunner.part1(get_data(20), limit=1) == 44 + + +def test_sample_part2() -> None: + assert DayRunner.part2(get_data(20), limit=50) == 285