i:3#'0:"i/21.in"                        / each code is 3 digits, then A (enter)
n:4 3#"789456123 0A"                    / numeric pad
d:2 3#" ^A<v>"                          / direction pad
s:{,//x#'\:("vvv^^^";">><<")}           / stringify direction vectors
e:{x@*<"<v^>"?*'x}@s'                   / most efficient route
u:{^(x.)y+*z}                           / unrecoverable route
t:{e(u[x;*y]')_|:\(=2)*-/|y:(,/&x=)'y}  / shortest path[pad;start,end]
g:{#'=2':t[x;y]/"AA"}                   / group[pad;input]
c:k!g[d]'k:(,/d)@+!6 6                  / cache
l:{+/x{+/(.x)*c@!x}/+/2g[n]':y/"AA"}    / length[robots;target]
p:{+/(l[x]'i)*.'i}                      / part[robots using direction pads]
p 2                                     / part 1: 2 robots using direction pads
p 25                                    / part 2: 25 robots using direction pads

Whew!

In part 1, what is a shortest path? Well, we want to group identical movements, so <<vv is preferred to <v<v. This still leaves us with two options: <<vv and vv<<. In the case where one of the sequences makes the robot panic, it's clear which to pick; but if we're left with the choice, we want to do lefts first, then downs, then ups, then rights.

For part 2, I used a cache that maps pairs to their expansion. For example, it maps the pair A< to the optimal moves needed to go from A to <.


~/aoc24k/21.html