There, elves are busy taking the lava to the factory in big crucibles (cups for molten stuff).
They want to get the crucibles to the factory as fast as possible to lose the least amount of heat.
Figuring out the ideal route to take is your job.
The elves prepared a map that list how much heat is lost if you enter a tile.
The starting location is in the top-left, the ending location in the bottom-right.
An example input looks like this:
Parsing
A 2D list of digits:
Part 1
The crucibles are unwieldy, pretty hard to “drive” (is that the right word? Hard to move.)
They cannot move in a straight line for more than 3 tiles.
They cannot reverse direction
The question asks for the minimum amount of heatloss when you reach the destination.
This is a shortest path problem, time to pull out the Dijkstra.
Helpers
To do some Dijkstra’s algorithm, I need a priority queue.
In Rust, I’ll use a BinaryHeap for this.
Some skeleton/pseudo-code I want to work towards:
The things I’ll push onto that queue and pop off it are my own data structures, Crucible:
The position it has is tracked by a Coord:
The direction the crucible is moving in is a Direction:
Now, to handle the soring of the Crucibles in that priority queue, I specified how they should be sorted, by cost, from low to high.
That way, every time I pop from the queue, I get the crucible with the lowest cost.
The next step is the function that determines the successors (a fancy word for the next possible crucibles).
It tries to move in every direction, and only does so if the rules from the question pass:
This handles some of the rules crucibles must adhere to, like:
Not moving more than 3 tiles in a straight line
Not reversing direction
The not reversing direction rule uses another helper:
It also makes sure a crucible doesn’t go off the map with that forward helper, it only adds a new crucible if it’s inside the grid.
That’s because that forward helper only returns a valid coordinate if it’s inside the grid:
Code
This worked, but went very slow, so I kept track of previous crucibles in a HashSet.
Only tracking the Crucible position is not enough, as it matters which direction the crucible is travelling in, and how far it already has in a straight line.
So every time I want to add a successor to the priority queue, I check if that (coord, dir, moves_in_dir) triplet has already been seen.
If it hasn’t, I add that crucible to the queue, and that triplet to the seen cache.
Part 2
It’s not going fast enough, to remedy this, the elves brought bigger crucibles, ultracrucibles.
They can move more lava, but are also even more unwieldy.
They need to move a minimum of 4 tiles in a single direction before they can turn or stop.
They can’t go further than 10 tiles in a single direction before they have to turn.
The question asks for the minimum amount of heatloss when you reach the destination.
Helpers
The nodes that go into the priority queue now are UltraCrucibles:
The same logic is used to order them:
And now, for the part that is different between part1 and part2, the successors:
This function encodes the movement rules laid out by the question.
The only other minor change is the check to see if the destination has been reached in the loop.
Since an UltraCrucible can’t stop immediately, I added a check to see if it has been moving for at least 4 tiles.