r/adventofcode • u/musifter • 22h ago
Other [2024 Day 21] In Review (Keypad Conundrum)
Today we're searching Santa's Reindeer-class starship. Unfortunately, a couple of The Historians have teleported into other parts of the ship behind doors that are locked by keypads. And having experienced Santa's ship before, it's not surprising to find that there are a lot of hazardous areas. And so we need to use a robot to deal with a numerical keypad in a depressurized area, and then another robot to access the panel to control that one in a radioactive area, and a second robot to access the control panel for that one in an area that's -40 degrees (which isn't so bad). All while we direct this chain from one safe area that's packed with Historians. And we can't just tell them what button to hit, we need to tell them how to move on the keypad grids and then to hit A to signal that the robot should hit the button. For part 2 we go after the second missing Historian with a chain of 25 intermediate robots.
The input for this one is very short. Five lines... each a three digit code to type, followed by A. Because, in addition to the above, we don't know which code we need because the The Historians have forgotten.
This was the slowest part 1 submission in 2024 for me. But a large part of that was that I spend a lot of time playing with the code to map out paths on the keypads... worrying about everything (building tables and carefully checking them). After which I started getting weird uninitialized warnings... it turns out that will happen when the keypad has two 6s and no 8.
I know my first solution for this was initially a mess, and was using paths between buttons that avoided hitting the same one twice on the way. As part of the cleanup I changed that to just the paths that head directly there (ie just getting all the step like patterns... these all naturally have the same length (Manhattan distance), which simplifies things a bit):
my @steps = (($delta->[0] < 0) ? '^' : 'v', ($delta->[1] < 0) ? '<' : '>');
And then a quick BFS using those moves.
The idea being to turn:
# Define the pads (with sentinel Xs)
my @keypad = map {[split //]} ('789X', '456X', '123X', 'X0AX', 'XXXX');
my @dirpad = map {[split //]} ('X^AX', '<v>X', 'XXXX');
Into a table of routes:
my %key_route = &route_pad( \%key_loc, \@keypad );
my %dir_route = &route_pad( \%dir_loc, \@dirpad );
Using BFS between all the starts and ends.
And for handling the chain, I went with recursion. The main trickiness here being that one of levels is not like the others (one has a number keypad, not a direction one). But that's easily done with selecting a table with:
my $routes = ($depth == 0) ? \%key_route : \%dir_route;
The other trickiness being the base case... where the last in the chain just adds the length of sequence (because we get to just type directly... which I suppose means there are two levels not like the others).
The final version ended up as this (and overly fancy I'll admit):
sub recurse_keypad {
my ($seq, $depth) = @_;
state %memo;
my $routes = ($depth == 0) ? \%key_route : \%dir_route;
my @seq = ('A', split( //, $seq ));
$memo{$seq,$depth} //= ($depth == $Max_Depth)
? sum chain { length( $routes->{$_[0],$_[1]}->[0] ) } @seq
: sum chain {
reduce {
min($a, &recurse_keypad($b, $depth+1))
} (~0, $routes->{$_[0],$_[1]}->@*)
} @seq;
}
Making use of my chain iterator that works on adjacent pairs of a list.
And in the end, although part 1 took over two hours, part 2 took about 20 minutes to fix up... a bit of cleaning, upping the depth, and throwing a memo on the function for speed. The end result is that I got part 2 in just fast enough to be top-1000 (the only time that happened in 2024 or 2023).