documentation and tiny speedup

This commit is contained in:
Eric Mertens 2023-01-30 12:25:39 -08:00
parent 30709c7bd7
commit 39c3d59370

View File

@ -3,9 +3,10 @@
///
/// This solution follows the following process:
/// 1. Parse the input source into a list of rooms
/// 2. Compute the shortest paths between each room
/// 3. Enumerate all the paths through the graph to find maximum water flow per valve-set.
/// 4. Use the flow/valve summaries to compute the two answers.
/// 2. Optimization: put rooms with valves first in the array
/// 3. Compute the shortest paths between each room
/// 4. Enumerate all the paths through the graph to find maximum water flow per valve-set.
/// 5. Use the flow/valve summaries to compute the two answers.
#include <bitset>
#include <cstdint>
@ -37,13 +38,16 @@ namespace {
template <typename T>
using distance_array = boost::multi_array<T, 2>;
/// @brief Update distance matrix with transitive shortest paths
/// @brief Update single-step distance matrix with transitive shortest paths
/// @tparam T distance type
/// @param dist Single-step distances between nodes (must be square)
/// @param[in,out] dist distance matrix
///
/// This implementation uses the FloydWarshall algorithm and assumes that
/// there are no negative-cost cycles. It also assumes that a path exists
/// between all pairs of nodes.
template <typename T>
auto ShortestDistances(distance_array<T> & dist) -> void
{
// FloydWarshall_algorithm
auto const range = boost::irange(dist.size());
for (auto const k : range) {
for (auto const i : range) {
@ -68,7 +72,7 @@ struct Room {
};
/// @brief Parse the input file
/// @param in input stream
/// @param[in,out] in input stream
/// @return Vector of parsed rooms, one per input line
///
/// The parser will consume input until the end of the stream.
@ -105,10 +109,24 @@ auto Parse(std::istream & in) -> std::vector<Room>
throw std::runtime_error{"bad input line"};
}
}
return result;
}
/// @brief Rearrange the rooms so that those with flows come first
/// @param[in,out] rooms vector of rooms that gets reordered
/// @return number of rooms with with non-zero flows
auto FlowsFirst(
std::vector<Room> & rooms
) -> std::size_t
{
// Put all of the rooms with vavles at the begining.
auto const zeros = boost::range::partition(rooms, [](auto const& room) { return room.flow > 0; });
return zeros - rooms.begin();
}
/// @brief Computes the distances between rooms and finds the address of the starting room
/// @param[in] rooms input list of rooms
/// @returns starting index and distances
auto GenerateDistances(
std::vector<Room> const& rooms
@ -161,13 +179,14 @@ struct State {
};
/// @brief Compute all the flows achievable with a set of values
/// @param start Index of starting room
/// @param initial_time Initial amount of time
/// @param rooms Array of rooms from input file
/// @param distances Shortest paths between all pairs of rooms
/// @param[in] start Index of starting room
/// @param[in] initial_time Initial amount of time
/// @param[in] rooms Array of rooms from input file
/// @param[in] distances Shortest paths between all pairs of rooms
/// @return Mapping of maximum flow achievable using a particular set of valves
auto Routes(
std::size_t const start,
std::size_t const interesting,
std::uint64_t const initial_time,
std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances
@ -176,14 +195,6 @@ auto Routes(
// Maximal flow seen at each set of open valves
std::unordered_map<Valves, std::uint64_t> result;
// Figure out which rooms have flow and are worth visiting at all
std::vector<std::size_t> interesting_rooms;
for (auto [i, room] : rooms | boost::adaptors::indexed()) {
if (room.flow > 0) {
interesting_rooms.push_back(i);
}
}
// Remaining states for depth first search
std::vector<State> states { State{initial_time, 0, start, {}} };
while (!states.empty()) {
@ -196,7 +207,7 @@ auto Routes(
auto const distances_i = distances[state.location];
for (auto const j : interesting_rooms) {
for (auto const j : boost::irange(interesting)) {
// don't revisit a valve
if (state.valves.test(j)) { continue; }
@ -218,32 +229,34 @@ auto Routes(
}
/// @brief Maximize the water flow using a single actor and 30 minutes
/// @param start Index of the starting room
/// @param rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms
/// @param[in] start Index of the starting room
/// @param[in] rooms Rooms from input file
/// @param[in] distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable
auto Part1(
std::size_t const start,
std::size_t const interesting,
std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances
) -> std::uint64_t
{
auto const routes = Routes(start, 30, rooms, distances);
auto const routes = Routes(start, interesting, 30, rooms, distances);
return *boost::range::max_element(routes | boost::adaptors::map_values);
}
/// @brief Maximize the water flow using two actos and 26 minutes
/// @param start Index of the starting room
/// @param rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms
/// @param[in] start Index of the starting room
/// @param[in] rooms Rooms from input file
/// @param[in] distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable
auto Part2(
std::size_t const start,
std::size_t const interesting,
std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances
) -> std::uint64_t
{
auto const routes = Routes(start, 26, rooms, distances);
auto const routes = Routes(start, interesting, 26, rooms, distances);
auto const end = routes.end();
std::uint64_t best {0};
@ -274,10 +287,11 @@ Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II
)"};
auto const rooms = Parse(in);
auto rooms = Parse(in);
auto const interesting = FlowsFirst(rooms);
auto const [start, distances] = GenerateDistances(rooms);
CHECK(1651 == Part1(start, rooms, distances));
CHECK(1707 == Part2(start, rooms, distances));
CHECK(1651 == Part1(start, interesting, rooms, distances));
CHECK(1707 == Part2(start, interesting, rooms, distances));
}
TEST_CASE("shortest path") {
@ -323,8 +337,9 @@ Valve JJ has flow rate=21; tunnel leads to valve II
/// @return 0 on success
auto main(int argc, char** argv) -> int
{
auto const rooms = Parse(*aocpp::Startup(argc, argv));
auto rooms = Parse(*aocpp::Startup(argc, argv));
auto const n = FlowsFirst(rooms);
auto const [start, distances] = GenerateDistances(rooms);
std::cout << "Part 1: " << Part1(start, rooms, distances) << std::endl;
std::cout << "Part 2: " << Part2(start, rooms, distances) << std::endl;
std::cout << "Part 1: " << Part1(start, n, rooms, distances) << std::endl;
std::cout << "Part 2: " << Part2(start, n, rooms, distances) << std::endl;
}