Solve 2024:21 pt2 "Keypad Conundrum"

Turns out the initial trial of using
Manhattan distance was required to
solve part 2, since the BFS
implementation is impossible to
scale up from 2 robots to 25.

Recursion and memoization makes the
execution time acceptable.

Line 53 makes all the difference here.
The order of `<|v|^|>` matters when
constructing a sequence. Many
hours was spent trying to find the correct
priority. For the example input, especially
456A and 379A were volatile.
This commit is contained in:
Anders Englöf Ytterström 2025-01-04 12:23:32 +01:00
parent 3a9a7d4994
commit d7d5312786
2 changed files with 21 additions and 83 deletions

View file

@ -29,7 +29,7 @@ from collections import deque, Counter, defaultdict
from heapq import heappop, heappush
from itertools import compress, combinations, chain, permutations
from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw
from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw, bk
def solve(data):

View file

@ -1,11 +1,5 @@
import re
from collections import deque, Counter, defaultdict
from heapq import heappop, heappush
from itertools import compress, combinations, chain, permutations
from functools import cache
from output import matrix, D, DD, DDa, ADJ, ints, mhd, mdbg, vdbg, cw, ccw
DKP = {
"^": (0, 1),
"A": (0, 2),
@ -32,98 +26,42 @@ NKP = {
def solve(data):
codes = data.split()
p1 = 0
p2 = 0
for code in codes:
seqlen = unwrap(code)
num = int(code[:-1])
p1 += num * seqlen
p2 = None
p1 += num * unfold(code, 1 + 2, num=True)
p2 += num * unfold(code, 1 + 25, num=True)
return p1, p2
def unwrap(dests):
seqlen = float("inf")
for num_path in press(dests, NKP):
for d0_path in press(num_path, DKP):
for d1_path in press(d0_path, DKP):
seqlen = min(seqlen, len(d1_path))
return seqlen
def press(dests, pad):
@cache
def unfold(sequence, iterations, num=False):
if iterations == 0:
return len(sequence)
s = "A"
P = [""]
for d in dests:
paths = bfs(tuple(pad.values()), pad[s], pad[d])
ml = len(sorted(paths, key=lambda x: len(x))[0])
sp = [p for p in paths if len(p) == ml]
nP = []
for a in P:
for b in sp:
nP.append(a + b)
P = nP
total = 0
pad, invalid = (NKP, (3, 0)) if num else (DKP, (0, 0))
for d in sequence:
total += unfold(seq(pad[s], pad[d], invalid), iterations - 1)
s = d
P = sorted(P, key=lambda x: len(x))
psl = len(P[0])
return [p for p in P if len(p) == psl]
return total
@cache
def bfs(grid, S, E):
seen = set()
Q = deque([(S, "<", "")])
paths = []
while Q:
pos, dn, path = Q.popleft()
if (pos, dn) in seen:
continue
if pos == E:
paths.append(path + "A")
continue
seen.add((pos, dn))
y, x = pos
for dn, delta in DDa.items():
dy, dx = delta
if (y + dy, x + dx) in grid:
Q.append(((y + dy, x + dx), dn, path + dn))
return paths
def seq(S, E, invalid):
y1, x1 = S
y2, x2 = E
seq = "<" * (x1 - x2) + "v" * (y2 - y1) + "^" * (y1 - y2) + ">" * (x2 - x1)
if (y2, x1) == invalid or (y1, x2) == invalid:
seq = seq[::-1]
return seq + "A"
if __name__ == "__main__":
import os
# use dummy data
inp = """
029A
980A
179A
456A
379A
""".strip()
"""
68 * 29
60 * 980
68 * 179
64 * 456
64 * 379
"""
# uncomment to instead use stdin
# import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input
with open("./input/21.txt", "r") as f:
inp = f.read().strip()
# uncomment to do initial data processing shared by part 1-2
p1, p2 = solve(inp)
print(p1)
os.system(f"echo {p1} | wl-copy")
# print(p2)
# os.system(f"echo {p2} | wl-copy")
# uncomment and replace 0 with actual output to refactor code
# and ensure nonbreaking changes
# assert p1 == 0
# assert p2 == 0
print(p2)