2024 day 21 part 2.
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Mikaël Capelle 2024-12-24 14:27:27 +01:00
parent d61d9d4559
commit 1e2db73b52

View File

@ -1,121 +1,85 @@
import heapq import itertools
from dataclasses import dataclass from functools import cache
from typing import Any, Iterator, Literal, Sequence, TypeAlias, cast from typing import Any, Iterator, Literal
from ..base import BaseSolver from ..base import BaseSolver
Action: TypeAlias = Literal[">", "<", "v", "^", "A"] NUM_PAD_P = {
v: (i, j)
NUM_PAD = ((7, 8, 9), (4, 5, 6), (1, 2, 3), (None, 0, "A")) for i, r in enumerate(("789", "456", "123", " 0A"))
MOV_PAD: tuple[tuple[Action | None, ...], ...] = ((None, "^", "A"), ("<", "v", ">")) for j, v in enumerate(r)
if v.strip()
}
MOV_PAD_P = {
v: (i, j)
for i, r in enumerate((" ^A", "<v>"))
for j, v in enumerate(r)
if v.strip()
}
@dataclass(frozen=True, order=True) def path(start: tuple[int, int], end: tuple[int, int], pad: Literal["num", "mov"]):
class Node: # a move in the grid is composed of at most two straight line: up/down and
robot_1: tuple[int, int] = (0, 2) # left/right, since doing some kind of diagonal moves would create long path for
robot_2: tuple[int, int] = (0, 2) # the robot above (since this involves going back-and-forth to the letter 'A')
robot_3: tuple[int, int] = (3, 2) #
row_s, col_s = start
row_e, col_e = end
code: str = "" le, de, ue, re = (
"<" * max(0, col_s - col_e),
"v" * max(0, row_e - row_s),
"^" * max(0, row_s - row_e),
">" * max(0, col_e - col_s),
)
# when the robot starts or ends on the row/column with the empty cell, there is
# only one way to move
#
if pad == "num" and (row_s, col_e) == (3, 0):
return ue + le
elif pad == "num" and (col_s, row_e) == (0, 3):
return re + de
elif pad == "mov" and col_s == 0:
return re + ue
elif pad == "mov" and col_e == 0:
return de + le
# otherwise, we need to decide if we want to go up/down first, or left/right, and
# apparently this is the best way to do it...
return le + de + ue + re
def apply_action( @cache
robot: tuple[int, int], def v_clicks(clicks: str, depth: int) -> int:
action: Action, if depth == 0:
pad: tuple[tuple[int | str | None, ...], ...], return len(clicks)
):
d_row, d_col = {"^": (-1, 0), "v": (1, 0), ">": (0, 1), "<": (0, -1)}[action]
row, col = robot[0] + d_row, robot[1] + d_col
if 0 <= row < len(pad) and 0 <= col < len(pad[row]) and pad[row][col] is not None: n_clicks = 0
return (row, col) at = "A"
for _, group in itertools.groupby(clicks):
group = list(group)
n_clicks += v_clicks(
path(MOV_PAD_P[at], MOV_PAD_P[group[0]], "mov") + "A" * len(group),
depth - 1,
)
at = group[0]
return None return n_clicks
def create_node(node: Node, action: Action) -> Node | None: def path_length(code: str, depth: int):
# main pad moves -> move first robot return sum(
if action != "A": v_clicks(path(NUM_PAD_P[start], NUM_PAD_P[end], "num") + "A", depth)
robot = apply_action(node.robot_1, action, MOV_PAD) for start, end in zip("A" + code[:-1], code, strict=True)
if robot is not None:
return Node(
robot_1=robot,
robot_2=node.robot_2,
robot_3=node.robot_3,
code=node.code,
)
return None
# activate pad 1 -> action on robot 1
robot_1_action = MOV_PAD[node.robot_1[0]][node.robot_1[1]]
assert robot_1_action is not None
if robot_1_action != "A":
robot2 = apply_action(node.robot_2, robot_1_action, MOV_PAD)
if robot2 is not None:
return Node(
robot_1=node.robot_1,
robot_2=robot2,
robot_3=node.robot_3,
code=node.code,
)
return None
# activate pad 2 -> action on robot 2
robot_2_action = MOV_PAD[node.robot_2[0]][node.robot_2[1]]
assert robot_2_action is not None
if robot_2_action != "A":
robot3 = apply_action(node.robot_3, robot_2_action, NUM_PAD)
if robot3 is not None:
return Node(
robot_1=node.robot_1,
robot_2=node.robot_2,
robot_3=robot3,
code=node.code,
)
return None
value = NUM_PAD[node.robot_3[0]][node.robot_3[1]]
assert value is not None
return Node(
robot_1=node.robot_1,
robot_2=node.robot_2,
robot_3=node.robot_3,
code=node.code + str(value),
) )
class Solver(BaseSolver): class Solver(BaseSolver):
def dijkstra_for_code(self, target: str):
queue: list[tuple[float, Node, tuple[str, ...]]] = [(0, Node(), ())]
preds: dict[Node, tuple[str, ...]] = {}
while queue:
dis, node, path = heapq.heappop(queue)
if not target.startswith(node.code):
continue
if node in preds:
continue
preds[node] = path
if node.code == target:
self.logger.info(f"found [{target}]: {''.join(path)} ({len(path)})")
return path
for action in cast(Sequence[Action], "A^v<>"):
node_2 = create_node(node, action)
if node_2:
heapq.heappush(queue, (dis + 1, node_2, path + (action,)))
return None
def solve(self, input: str) -> Iterator[Any]: def solve(self, input: str) -> Iterator[Any]:
yield sum( yield sum(
len(self.dijkstra_for_code(code) or ()) * int(code[:-1], 10) path_length(code, 2) * int(code[:-1], 10) for code in input.splitlines()
for code in input.splitlines() )
yield sum(
path_length(code, 25) * int(code[:-1], 10) for code in input.splitlines()
) )