It came from an other communication device just like yours.
An elephant is standing beside it, it must have triggered a distress signal somehow, clever elephant.
The vulcano will erupt in 30 minutes!
There’s not enough time to leave the way you came.
There appear to be a series of pressure-release valves and pipes nearby.
Each valve has a flow rate with a number for relieved pressure per minute if opened.
You can use the pipes to move between valves.
You want to relieve as much pressure as you can in those 30 minutes.
Your device scans the network of valves and pipes.
Today’s input is the results of that scan.
Some of the valves are blocked, and opening them does not result in relieved pressure.
An example input looks like this:
Opening a valve takes exactly 1 minute.
Traversing a pipe takes exactly 1 minute.
Parsing
A valve in the input can be represented by:
and each Valve has a name.
The series of valves and pipes can be represented as a HashMap<&str, Valve>:
Part 1
The question asks what the largest amount of pressure you can release in 30 minutes is.
I want to tackle this problem in 2 parts.
figuring out the shortest path in minutes from any flowing valve (and the start) to an other flowing valve
figuring out the greatest relieved pressure by checking the relieved pressure when visiting every combination of those locations
That way, this problem is reduced to 2 more manageable problems.
A bunch of shortest path problems (Hello Dijkstra my old friend)
A travelling salesman-ish problem
Helpers
A helper to calculate the shortest path from one valve to an other valve.
Now the helper function I wrote in the pseudocode above.
It creates a map with shortest distances:
key: (from_name, to_name)
value: move_cost
It starts by only keeping the flowing valves from the input.
It records the shortest distance from our starting point "AA" to that valve.
It records the shortest distance from that flowing valve to every other flowing valve.
With that done, time for the simulation.
We represent each state in the simulation as a State struct.
It has information about the state a simulation is currently in:
which valves are open (starting at an empty set)
which room we are in (starting at "AA")
how many time has elapsed (starting at 0)
how much pressure was relieved so far (starting at 0)
We start at:
and insert that state into a queue.
Then, every iteration of the loop, we pop off a state and continue until the queue is empty.
Inside the loop:
If all valves are open, there’s nothing more we can do but wait until the end and let all the valves relieve pressure.
If the time limit has been met, we record how much pressure has been relieved and move on to the next state in the queue.
The helper function is quite short.
Given a few parameters about the state and a time limit, it returns how much pressure would be relieved when that time limit is reached.
Then, for every choice of valve to open next, we potentially add a new state to the queue.
We only consider flowing valves, moving to a non-flowing one makes no sense.
We know how long it will take to move there because we have a dist_map.
Opening that valve will take 1 additional minute.
We add the valve we just opened to the opened for that new state.
We also know the elapsed time for that new state now:
The time that was already elapsed + the cost to move + 1 to open the valve.
We also know the relieved for that new state:
While we move, and open the valve, pressure releases.
If the new elapsed time would exceed (or match) the time limit.
We calculate how much pressure would be relieved by the valves that are already open using our wait_until_ending helper and update our max_relieved variable.
Eventually, the queue empties.
The value in max_relieved at that time is the maximum that can possibly be relieves in that time limit.
To speed the algorithm up a bit, I added a seen set.
That set contains items that are almost the same as a State, but the current position doesn’t matter.
We only add a new state to the queue if we haven’t seen a state with the same valves open that’s also at the same elapsed time, and has the same amount relieved.
Final code
Part 2
Wait a minute, that’s one smart, magical elephant.
You can teach it to open valves.
It takes you 4 minutes to teach the elephant how to open a series of valves.
After that, there are 26 minutes left and you both start opening valves.
The question asks what the largest amount of pressure is that you and an elephant working together for 26 minutes could release.
For part2, run the same simulation.
Record the maximum relieved pressure per opened valve combination at the end.
It doesn’t matter in wich order they were opened, only which ones were, and the maximum amount of pressure that was released at the end.
To track this, introduce a map that starts off empty before we even start the simulation.
While going through the simulation (every time a state is popped off the queue).
Calculate how much pressure would be released at the end with that combination of open valves.
If it beats the number that already exists for a same combination, update it.
We exited the loop and arrived at the end of the part2 function.
The elephant and the human both have to choose a path to take that results in the most relieved pressure when combined.
That means they each have to take a path that visits valves that don’t overlap!
Trying to open the same valve twice is a waste of effort.
In our code that means that the set of visited valves can not have overlap, it has to be disjoint.
The path they each take for a combination of valves is the one with the most relieved pressure at the end.
We tracked those, in max_relieved_states.