Skip to content

Commit 40632c8

Browse files
committed
Implement 2024 day 21 part 1
1 parent 073b576 commit 40632c8

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

2024/src/aoc/days/day21.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import functools
2+
3+
from . import SeparateRunner
4+
5+
NUMPAD = {
6+
"A": (3, 2),
7+
"0": (3, 1),
8+
"1": (2, 0),
9+
"2": (2, 1),
10+
"3": (2, 2),
11+
"4": (1, 0),
12+
"5": (1, 1),
13+
"6": (1, 2),
14+
"7": (0, 0),
15+
"8": (0, 1),
16+
"9": (0, 2),
17+
}
18+
19+
DIRPAD = {
20+
"A": (0, 2),
21+
"^": (0, 1),
22+
"<": (1, 0),
23+
"v": (1, 1),
24+
">": (1, 2),
25+
}
26+
27+
28+
@functools.cache
29+
def shortest_numpad(from_: str, to: str) -> list[str]:
30+
inverse = set(NUMPAD.values())
31+
ay, ax = NUMPAD[from_]
32+
by, bx = NUMPAD[to]
33+
34+
dx, dy = bx - ax, by - ay
35+
36+
sx = "<" if dx < 0 else ">"
37+
sy = "^" if dy < 0 else "v"
38+
39+
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
40+
return abs(dy) * sy + abs(dx) * sx + "A"
41+
else:
42+
return abs(dx) * sx + abs(dy) * sy + "A"
43+
44+
45+
@functools.cache
46+
def shortest_dirpad(from_: str, to: str) -> str:
47+
inverse = set(DIRPAD.values())
48+
ay, ax = DIRPAD[from_]
49+
by, bx = DIRPAD[to]
50+
51+
dx, dy = bx - ax, by - ay
52+
sx = "<" if dx < 0 else ">"
53+
sy = "^" if dy < 0 else "v"
54+
55+
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
56+
return abs(dy) * sy + abs(dx) * sx + "A"
57+
else:
58+
return abs(dx) * sx + abs(dy) * sy + "A"
59+
60+
61+
def encode_shortest_numpad(code: str) -> str:
62+
pos = "A"
63+
64+
res = ""
65+
66+
for c in code:
67+
res += shortest_numpad(pos, c)
68+
# print(c, res)
69+
pos = c
70+
71+
return res
72+
73+
74+
def encode_shortest_dirpad(code: str) -> str:
75+
pos = "A"
76+
77+
res = ""
78+
79+
for c in code:
80+
if pos != c:
81+
res += shortest_dirpad(pos, c)
82+
else:
83+
res += "A"
84+
pos = c
85+
86+
return res
87+
88+
89+
def decode(code: str, pad: dict[str, tuple[int, int]]) -> str:
90+
result = ""
91+
inverse = {v: k for k, v in pad.items()}
92+
93+
y, x = pad["A"]
94+
95+
for i, c in enumerate(code):
96+
match c:
97+
case "A":
98+
result += inverse[y, x]
99+
case "^":
100+
y -= 1
101+
case "v":
102+
y += 1
103+
case "<":
104+
x -= 1
105+
case ">":
106+
x += 1
107+
108+
if (y, x) not in inverse:
109+
raise ValueError(
110+
f"""Moved off the board {x, y}, after processing {c}.
111+
Path so far: {result} (from {code[:i]})"""
112+
)
113+
114+
return result
115+
116+
117+
class DayRunner(SeparateRunner):
118+
@classmethod
119+
def part1(cls, input: str) -> int:
120+
result = 0
121+
for code in input.strip().split("\n"):
122+
numpad = encode_shortest_numpad(code)
123+
robot1 = encode_shortest_dirpad(numpad)
124+
robot2 = encode_shortest_dirpad(robot1)
125+
126+
result += int(code[:-1]) * len(robot2)
127+
128+
return result
129+
130+
@classmethod
131+
def part2(cls, input: str) -> int:
132+
pass

2024/tests/samples/21.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
029A
2+
980A
3+
179A
4+
456A
5+
379A

2024/tests/test_day21.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
3+
from aoc.days.day21 import (
4+
DayRunner,
5+
encode_shortest_dirpad,
6+
encode_shortest_numpad,
7+
)
8+
9+
from . import get_data
10+
11+
12+
def test_encode_shortest_numpad() -> None:
13+
assert encode_shortest_numpad("029A") in (
14+
"<A^A>^^AvvvA",
15+
"<A^A^>^AvvvA",
16+
"<A^A^^>AvvvA",
17+
)
18+
19+
20+
def test_encode_shortest_dirpad() -> None:
21+
numpad_encoded = encode_shortest_numpad("029A")
22+
assert len(encode_shortest_dirpad(numpad_encoded)) == len(
23+
"v<<A>>^A<A>AvA<^AA>A<vAAA>^A"
24+
)
25+
26+
27+
@pytest.mark.parametrize(
28+
"code,answer",
29+
[
30+
(
31+
"029A",
32+
"<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A",
33+
),
34+
("980A", "<v<A>>^AAAvA^A<vA<AA>>^AvAA<^A>A<v<A>A>^AAAvA<^A>A<vA>^A<A>A"),
35+
(
36+
"179A",
37+
"<v<A>>^A<vA<A>>^AAvAA<^A>A<v<A>>^AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A",
38+
),
39+
("456A", "<v<A>>^AA<vA<A>>^AAvAA<^A>A<vA>^A<A>A<vA>^A<A>A<v<A>A>^AAvA<^A>A"),
40+
("379A", "<v<A>>^AvA^A<vA<AA>>^AAvA<^A>AAvA^A<vA>^AA<A>A<v<A>A>^AAAvA<^A>A"),
41+
],
42+
)
43+
def test_encode_shortest_dirpad_twice(code: str, answer: str) -> None:
44+
numpad_encoded = encode_shortest_numpad(code)
45+
robot1 = encode_shortest_dirpad(numpad_encoded)
46+
robot2 = encode_shortest_dirpad(robot1)
47+
assert len(robot2) == len(answer)
48+
49+
50+
def test_sample_part1() -> None:
51+
assert DayRunner.part1(get_data(21)) == 126384

0 commit comments

Comments
 (0)