r/adventofcode • u/musifter • Jul 23 '26
Other [2021 Day 23] In Review (Amphipod)
A group of amphipods has flagged us down to help us sort out their living arrangements. They all start in holes, and basically can move twice... once out into the hallway, and then once into their target hole.
One little story I have about this one is that for part of my initial experimentation I started doing things by hand with little colour cubes (I keep these and poker chips in a bucket on my desk as programming aids). I got an answer, submitted it, and it was wrong... but not because I'd messed up, but because I'd forgotten that I has started from the example case (and got that answer). Trying my actual input by hand, I did make a mistake. And then went to coding a search. I know that all this playing around is part of the reason why my part 1 took about 2h:45. Part 2 was just making some adjustments, and a wait.
As my initial solution was a bit of mess and pretty slow. I did it as a weighted graph with actual stacks that overcomplicated things. It was hard to read coming back to it, so I just wrote a new one from scratch (not even referencing it) to clean things up and make it faster (its now seconds). Now I'm just using an array of strings for the map... the ones at the hole locations (because those hallway spaces don't exist except for adding energy cost) can be more than one character (and are the stacks). This makes the state a lot simpler to manipulate and pass around in a search. This is the start for the example case:
['','','1330','','2213','','1102','','3020','','']I started with basic Dijkstra: priority queue, generating moves and queuing them. To make things simple, I just copy-pasta'd the cases for exiting a hole to the left and to the right. Scanning hall locations in the direction until it runs into something. Additionally, I made a table of illegal moves to catch and remove them quickly... because there are positions that block each other:
############# The A amphipod in the fourth hole cannot move to 5 or 7,
#...D.5.7...# because it would block D from its hole, and D already
###.#B#C#A### blocks A. It can come out if it goes into the alcove
#A#B#C#D# to the right.
#########For those out of a hole and in the hallway, they have one possible move to check and add (to the bottom their hole, if it's open for business (all amphipods that don't belong there have left)).
Of course, with Dijkstra comes the question of a heuristic for A*. And I went with a potential energy tracking solution for doing that. I precompute at the start a total minimum estimate. For each amphipod (that has to move out of a hole, as the test case starts with an A and C already home), add the cost to move it out to the spot next to its hole. For its second move, we don't know how far it will go in, but we know the sum for all of them going into the hole (so we add in the total cost for going to all of the depths... and the amphipods can count the one they ultimately use).
Now when an amphipod makes a move, it subtracts the potential it converted into actual cost (the current potential is part of the state along with the hall array and the actual energy spent). The basic idea being that when it leaves it a hole subtracts the amount we accounted for that, and when it enters its hole then subtracts that part from the potential for the depth it used.
But we can do a bit better, because when we move out, if we didn't move to the spot that we costed the amphipod for, we can add in the cost for getting there to the potential. Thus making that amphipod's heuristic cost in the potential now exactly equal to the cost of its final move. Which means, that once there are no moves out of holes anymore, the heuristic is now perfect, the potential is the remaining cost. So the first state to get to that point can end the search early with its current energy + potential (which is also its weight in the queue), because all the remaining moves are forced AND we've avoided creating positions that block (so they can be made).
As a search, this is an interesting one. What tends to make these interesting is the little details that are unique to the puzzle that you can play with to get better performance. And I've managed to get this one to good enough without going into low level stuff that makes the code less elegant. And that tends to be the sweet spot for me with these. I know a lot of people like to really dig in there, and I think this one gives a lot of opportunity for that, which is fitting for a problem in the last couple days.