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: /// This solution follows the following process:
/// 1. Parse the input source into a list of rooms /// 1. Parse the input source into a list of rooms
/// 2. Compute the shortest paths between each room /// 2. Optimization: put rooms with valves first in the array
/// 3. Enumerate all the paths through the graph to find maximum water flow per valve-set. /// 3. Compute the shortest paths between each room
/// 4. Use the flow/valve summaries to compute the two answers. /// 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 <bitset>
#include <cstdint> #include <cstdint>
@ -37,13 +38,16 @@ namespace {
template <typename T> template <typename T>
using distance_array = boost::multi_array<T, 2>; 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 /// @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> template <typename T>
auto ShortestDistances(distance_array<T> & dist) -> void auto ShortestDistances(distance_array<T> & dist) -> void
{ {
// FloydWarshall_algorithm
auto const range = boost::irange(dist.size()); auto const range = boost::irange(dist.size());
for (auto const k : range) { for (auto const k : range) {
for (auto const i : range) { for (auto const i : range) {
@ -68,7 +72,7 @@ struct Room {
}; };
/// @brief Parse the input file /// @brief Parse the input file
/// @param in input stream /// @param[in,out] in input stream
/// @return Vector of parsed rooms, one per input line /// @return Vector of parsed rooms, one per input line
/// ///
/// The parser will consume input until the end of the stream. /// 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"}; throw std::runtime_error{"bad input line"};
} }
} }
return result; 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 /// @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 /// @returns starting index and distances
auto GenerateDistances( auto GenerateDistances(
std::vector<Room> const& rooms std::vector<Room> const& rooms
@ -161,13 +179,14 @@ struct State {
}; };
/// @brief Compute all the flows achievable with a set of values /// @brief Compute all the flows achievable with a set of values
/// @param start Index of starting room /// @param[in] start Index of starting room
/// @param initial_time Initial amount of time /// @param[in] initial_time Initial amount of time
/// @param rooms Array of rooms from input file /// @param[in] rooms Array of rooms from input file
/// @param distances Shortest paths between all pairs of rooms /// @param[in] distances Shortest paths between all pairs of rooms
/// @return Mapping of maximum flow achievable using a particular set of valves /// @return Mapping of maximum flow achievable using a particular set of valves
auto Routes( auto Routes(
std::size_t const start, std::size_t const start,
std::size_t const interesting,
std::uint64_t const initial_time, std::uint64_t const initial_time,
std::vector<Room> const& rooms, std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances distance_array<std::uint64_t> const& distances
@ -176,14 +195,6 @@ auto Routes(
// Maximal flow seen at each set of open valves // Maximal flow seen at each set of open valves
std::unordered_map<Valves, std::uint64_t> result; 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 // Remaining states for depth first search
std::vector<State> states { State{initial_time, 0, start, {}} }; std::vector<State> states { State{initial_time, 0, start, {}} };
while (!states.empty()) { while (!states.empty()) {
@ -196,7 +207,7 @@ auto Routes(
auto const distances_i = distances[state.location]; 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 // don't revisit a valve
if (state.valves.test(j)) { continue; } if (state.valves.test(j)) { continue; }
@ -218,32 +229,34 @@ auto Routes(
} }
/// @brief Maximize the water flow using a single actor and 30 minutes /// @brief Maximize the water flow using a single actor and 30 minutes
/// @param start Index of the starting room /// @param[in] start Index of the starting room
/// @param rooms Rooms from input file /// @param[in] rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms /// @param[in] distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable /// @return Maximum flow achievable
auto Part1( auto Part1(
std::size_t const start, std::size_t const start,
std::size_t const interesting,
std::vector<Room> const& rooms, std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances distance_array<std::uint64_t> const& distances
) -> std::uint64_t ) -> 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); return *boost::range::max_element(routes | boost::adaptors::map_values);
} }
/// @brief Maximize the water flow using two actos and 26 minutes /// @brief Maximize the water flow using two actos and 26 minutes
/// @param start Index of the starting room /// @param[in] start Index of the starting room
/// @param rooms Rooms from input file /// @param[in] rooms Rooms from input file
/// @param distances Shortest distances between pairs of rooms /// @param[in] distances Shortest distances between pairs of rooms
/// @return Maximum flow achievable /// @return Maximum flow achievable
auto Part2( auto Part2(
std::size_t const start, std::size_t const start,
std::size_t const interesting,
std::vector<Room> const& rooms, std::vector<Room> const& rooms,
distance_array<std::uint64_t> const& distances distance_array<std::uint64_t> const& distances
) -> std::uint64_t ) -> 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(); auto const end = routes.end();
std::uint64_t best {0}; 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 II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II 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); auto const [start, distances] = GenerateDistances(rooms);
CHECK(1651 == Part1(start, rooms, distances)); CHECK(1651 == Part1(start, interesting, rooms, distances));
CHECK(1707 == Part2(start, rooms, distances)); CHECK(1707 == Part2(start, interesting, rooms, distances));
} }
TEST_CASE("shortest path") { TEST_CASE("shortest path") {
@ -323,8 +337,9 @@ Valve JJ has flow rate=21; tunnel leads to valve II
/// @return 0 on success /// @return 0 on success
auto main(int argc, char** argv) -> int 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); auto const [start, distances] = GenerateDistances(rooms);
std::cout << "Part 1: " << Part1(start, rooms, distances) << std::endl; std::cout << "Part 1: " << Part1(start, n, rooms, distances) << std::endl;
std::cout << "Part 2: " << Part2(start, rooms, distances) << std::endl; std::cout << "Part 2: " << Part2(start, n, rooms, distances) << std::endl;
} }