From 39c3d593704087d3a813713dfbda37c36849b0ea Mon Sep 17 00:00:00 2001 From: Eric Mertens Date: Mon, 30 Jan 2023 12:25:39 -0800 Subject: [PATCH] documentation and tiny speedup --- 2022/16.cpp | 83 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/2022/16.cpp b/2022/16.cpp index 6558c36..21ef991 100644 --- a/2022/16.cpp +++ b/2022/16.cpp @@ -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 #include @@ -37,13 +38,16 @@ namespace { template using distance_array = boost::multi_array; -/// @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 Floyd–Warshall algorithm and assumes that +/// there are no negative-cost cycles. It also assumes that a path exists +/// between all pairs of nodes. template auto ShortestDistances(distance_array & dist) -> void { - // Floyd–Warshall_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 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 & 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 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 const& rooms, distance_array const& distances @@ -176,14 +195,6 @@ auto Routes( // Maximal flow seen at each set of open valves std::unordered_map result; - // Figure out which rooms have flow and are worth visiting at all - std::vector 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 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 const& rooms, distance_array 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 const& rooms, distance_array 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; }