Skip to content

Commit 09aa6c8

Browse files
committed
2024 day 16
1 parent 9f8446f commit 09aa6c8

7 files changed

Lines changed: 298 additions & 1 deletion

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
###############
2+
#.......#....E#
3+
#.#.###.#.###.#
4+
#.....#.#...#.#
5+
#.###.#####.#.#
6+
#.#.#.......#.#
7+
#.#.#####.###.#
8+
#...........#.#
9+
###.#.#####.#.#
10+
#...#.....#.#.#
11+
#.#.#.###.#.#.#
12+
#.....#...#.#.#
13+
#.###.#.#.#.#.#
14+
#S..#.....#...#
15+
###############
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################
2+
#...#...#...#..E#
3+
#.#.#.#.#.#.#.#.#
4+
#.#.#.#...#...#.#
5+
#.#.#.#.###.#.#.#
6+
#...#.#.#.....#.#
7+
#.#.#.#.#.#####.#
8+
#.#...#.#.#.....#
9+
#.#.#####.#.###.#
10+
#.#.#.......#...#
11+
#.#.###.#####.###
12+
#.#.#...#.....#.#
13+
#.#.#.#####.###.#
14+
#.#.#.........#.#
15+
#.#.#.#########.#
16+
#S#.............#
17+
#################

2024/python/data/day16/input.txt

Lines changed: 141 additions & 0 deletions
Large diffs are not rendered by default.

2024/python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ description = "Advent of Code 2024"
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
8+
"frozendict>=2.4.7",
89
"z3-solver>=4.15.4.0",
910
]
1011

2024/python/src/aoc2024/day16.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from __future__ import annotations
2+
3+
import heapq
4+
import itertools
5+
from dataclasses import dataclass
6+
from pathlib import Path
7+
8+
from frozendict import frozendict
9+
10+
11+
def part_1(input: Path) -> int:
12+
start, end, walls = parse_input(input)
13+
lowest_scores = find_lowest_scores(start, end, walls)
14+
return min([score for state, score in lowest_scores.items() if state.p == end])
15+
16+
17+
def parse_input(input: Path) -> tuple[P, P, frozenset[P]]:
18+
start: P | None = None
19+
end: P | None = None
20+
walls: set[P] = set()
21+
for y, line in enumerate(input.read_text().splitlines()):
22+
for x, c in enumerate(line):
23+
if c == "#":
24+
walls.add(P(x, y))
25+
elif c == "S":
26+
start = P(x, y)
27+
elif c == "E":
28+
end = P(x, y)
29+
assert start is not None
30+
assert end is not None
31+
return start, end, frozenset(walls)
32+
33+
34+
def find_lowest_scores(start: P, end: P, walls: frozenset[P]) -> frozendict[State, int]:
35+
counter = itertools.count()
36+
q = [(0, next(counter), State(start, P(1, 0)))]
37+
scores = {}
38+
while q:
39+
score, _, state = heapq.heappop(q)
40+
if state in scores:
41+
continue
42+
43+
scores[state] = score
44+
45+
heapq.heappush(
46+
q,
47+
(score + 1000, next(counter), State(state.p, CLOCKWISE[state.d])), # ty: ignore[invalid-argument-type]
48+
)
49+
heapq.heappush(
50+
q,
51+
(score + 1000, next(counter), State(state.p, ANTICLOCKWISE[state.d])), # ty: ignore[invalid-argument-type]
52+
)
53+
if state.p + state.d not in walls:
54+
heapq.heappush(
55+
q,
56+
(score + 1, next(counter), State(state.p + state.d, state.d)),
57+
)
58+
59+
return frozendict(scores)
60+
61+
62+
@dataclass(frozen=True)
63+
class State:
64+
p: P
65+
d: P
66+
67+
68+
@dataclass(frozen=True)
69+
class P:
70+
x: int
71+
y: int
72+
73+
def __add__(self, other: P) -> P:
74+
return P(self.x + other.x, self.y + other.y)
75+
76+
77+
CLOCKWISE = frozendict(
78+
{
79+
P(1, 0): P(0, 1),
80+
P(0, 1): P(-1, 0),
81+
P(-1, 0): P(0, -1),
82+
P(0, -1): P(1, 0),
83+
}
84+
)
85+
86+
ANTICLOCKWISE = frozendict(
87+
{
88+
P(1, 0): P(0, -1),
89+
P(0, 1): P(1, 0),
90+
P(-1, 0): P(0, 1),
91+
P(0, -1): P(-1, 0),
92+
}
93+
)

2024/python/tests/test_day16.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pathlib import Path
2+
3+
from aoc2024 import day16
4+
5+
DATA_DIR = Path(__file__).parent.parent / "data/day16"
6+
7+
8+
def test_part_1():
9+
assert day16.part_1(DATA_DIR / "example_1.txt") == 7036
10+
assert day16.part_1(DATA_DIR / "example_2.txt") == 11048
11+
assert day16.part_1(DATA_DIR / "input.txt") == 127520
12+
13+
14+
# def test_part_2():
15+
# assert day16.part_2(DATA_DIR / "example_1.txt") == 45
16+
# assert day16.part_2(DATA_DIR / "example_2.txt") == 64
17+
# assert day16.part_2(DATA_DIR / "input.txt") == 42

2024/python/uv.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)