diff --git a/2024/src/aoc/days/day18.py b/2024/src/aoc/days/day18.py new file mode 100644 index 0000000..c5783c0 --- /dev/null +++ b/2024/src/aoc/days/day18.py @@ -0,0 +1,70 @@ +from collections import deque + +from . import SeparateRunner + + +def parse_input(data: str) -> list[tuple[int, int]]: + return [tuple(map(int, line.split(","))) for line in data.strip().split("\n")] + + +def find_exit(fallen: set[tuple[int, int]], width: int, height: int) -> int | None: + todo = deque([(0, 0, 0)]) + + best = {(0, 0): 0} + + def enqueue(dist: int, x: int, y: int): + # print(f"trying {x},{y}") + if (x, y) in fallen: + return + + if (x, y) not in best or best[x, y] > dist: + best[x, y] = dist + todo.append((dist, x, y)) + + while todo: + dist, x, y = todo.popleft() + # print(x, y) + + if x == width - 1 and y == height - 1: + return dist + + if x > 0: + enqueue(dist + 1, x - 1, y) + + if x + 1 < width: + enqueue(dist + 1, x + 1, y) + + if y > 0: + enqueue(dist + 1, x, y - 1) + + if y + 1 < height: + enqueue(dist + 1, x, y + 1) + + +class DayRunner(SeparateRunner): + @classmethod + def part1( + cls, input: str, width: int = 71, height: int = 71, limit: int = 1024 + ) -> int: + falling = parse_input(input) + + return find_exit(set(falling[:limit]), width, height) + + @classmethod + def part2(cls, input: str, width: int = 71, height: int = 71) -> str: + falling = parse_input(input) + + lower = 0 + upper = len(falling) + + while lower < upper: + mid = lower + (upper - lower) // 2 + + if find_exit(set(falling[:mid]), width, height) is not None: + lower = mid + 1 + else: + upper = mid + + first_blocker = falling[lower - 1] + + return f"{first_blocker[0]},{first_blocker[1]}" diff --git a/2024/tests/samples/18.txt b/2024/tests/samples/18.txt new file mode 100644 index 0000000..79c8583 --- /dev/null +++ b/2024/tests/samples/18.txt @@ -0,0 +1,25 @@ +5,4 +4,2 +4,5 +3,0 +2,1 +6,3 +2,4 +1,5 +0,6 +3,3 +2,6 +5,1 +1,2 +5,5 +2,5 +6,5 +1,4 +0,4 +6,4 +1,1 +6,1 +1,0 +0,5 +1,6 +2,0 diff --git a/2024/tests/test_day18.py b/2024/tests/test_day18.py new file mode 100644 index 0000000..6f329bd --- /dev/null +++ b/2024/tests/test_day18.py @@ -0,0 +1,11 @@ +from aoc.days.day18 import DayRunner + +from . import get_data + + +def test_sample_part1() -> None: + assert DayRunner.part1(get_data(18), width=7, height=7, limit=12) == 22 + + +def test_sample_part2() -> None: + assert DayRunner.part2(get_data(18), width=7, height=7) == "6,1"