This commit is contained in:
parent
d61d9d4559
commit
1e2db73b52
@ -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()
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user