documentation
This commit is contained in:
parent
499fa902fd
commit
30709c7bd7
137
2022/16.cpp
137
2022/16.cpp
|
@ -1,3 +1,12 @@
|
||||||
|
/// @file 16.cpp
|
||||||
|
/// @brief Solution to Advent of Code 2022 Day 16
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -21,11 +30,18 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using distance_array = boost::multi_array<std::uint64_t, 2>;
|
/// @brief Array of distances from one node to another.
|
||||||
|
/// @tparam T distance type
|
||||||
|
///
|
||||||
|
/// `distance[i][j]` is the cost to move from node `i` to node `j`
|
||||||
|
template <typename T>
|
||||||
|
using distance_array = boost::multi_array<T, 2>;
|
||||||
|
|
||||||
/// @brief Update distance matrix with transitive shortest paths
|
/// @brief Update distance matrix with transitive shortest paths
|
||||||
|
/// @tparam T distance type
|
||||||
/// @param dist Single-step distances between nodes (must be square)
|
/// @param dist Single-step distances between nodes (must be square)
|
||||||
auto ShortestDistances(distance_array & dist) -> void
|
template <typename T>
|
||||||
|
auto ShortestDistances(distance_array<T> & dist) -> void
|
||||||
{
|
{
|
||||||
// Floyd–Warshall_algorithm
|
// Floyd–Warshall_algorithm
|
||||||
auto const range = boost::irange(dist.size());
|
auto const range = boost::irange(dist.size());
|
||||||
|
@ -40,6 +56,8 @@ auto ShortestDistances(distance_array & dist) -> void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @struct Room
|
||||||
|
/// @brief A single record from the problem input.
|
||||||
struct Room {
|
struct Room {
|
||||||
/// @brief Name of the room
|
/// @brief Name of the room
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -49,6 +67,15 @@ struct Room {
|
||||||
std::vector<std::string> connections;
|
std::vector<std::string> connections;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief Parse the input file
|
||||||
|
/// @param in input stream
|
||||||
|
/// @return Vector of parsed rooms, one per input line
|
||||||
|
///
|
||||||
|
/// The parser will consume input until the end of the stream.
|
||||||
|
///
|
||||||
|
/// Input lines should follow the following pattern:
|
||||||
|
/// * Valve **name** has flow rate= **number** ; tunnels lead to valves **name**, **name** ...
|
||||||
|
/// * Valve **name** has flow rate= **number** ; tunnel leads to valve **name**
|
||||||
auto Parse(std::istream & in) -> std::vector<Room>
|
auto Parse(std::istream & in) -> std::vector<Room>
|
||||||
{
|
{
|
||||||
std::vector<Room> result;
|
std::vector<Room> result;
|
||||||
|
@ -74,7 +101,7 @@ auto Parse(std::istream & in) -> std::vector<Room>
|
||||||
It b = line.begin();
|
It b = line.begin();
|
||||||
It e = line.end();
|
It e = line.end();
|
||||||
result.emplace_back();
|
result.emplace_back();
|
||||||
if (!qi::parse(b, e, room_description, result.back())) {
|
if (!qi::parse(b, e, room_description, result.back()) || b != e) {
|
||||||
throw std::runtime_error{"bad input line"};
|
throw std::runtime_error{"bad input line"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +112,7 @@ auto Parse(std::istream & in) -> std::vector<Room>
|
||||||
/// @returns starting index and distances
|
/// @returns starting index and distances
|
||||||
auto GenerateDistances(
|
auto GenerateDistances(
|
||||||
std::vector<Room> const& rooms
|
std::vector<Room> const& rooms
|
||||||
) -> std::pair<std::size_t, distance_array>
|
) -> std::pair<std::size_t, distance_array<std::uint64_t>>
|
||||||
{
|
{
|
||||||
auto const N = rooms.size();
|
auto const N = rooms.size();
|
||||||
|
|
||||||
|
@ -95,7 +122,7 @@ auto GenerateDistances(
|
||||||
names[room.name] = i;
|
names[room.name] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
distance_array distances{boost::extents[N][N]};
|
distance_array<std::uint64_t> distances{boost::extents[N][N]};
|
||||||
|
|
||||||
// N is longer than any optimal distance by at least 1
|
// N is longer than any optimal distance by at least 1
|
||||||
std::fill_n(distances.data(), distances.num_elements(), N);
|
std::fill_n(distances.data(), distances.num_elements(), N);
|
||||||
|
@ -117,12 +144,19 @@ auto GenerateDistances(
|
||||||
return {names.at("AA"), std::move(distances)};
|
return {names.at("AA"), std::move(distances)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Bitset used to track which valves have been turned on
|
||||||
using Valves = std::bitset<64>;
|
using Valves = std::bitset<64>;
|
||||||
|
|
||||||
|
/// @struct State
|
||||||
|
/// @brief Intermediate states for depth-first search in Routes
|
||||||
struct State {
|
struct State {
|
||||||
|
/// @brief Time remaining
|
||||||
std::uint64_t time;
|
std::uint64_t time;
|
||||||
|
/// @brief Water flow achieved so far
|
||||||
std::uint64_t flow;
|
std::uint64_t flow;
|
||||||
|
/// @brief Current actor location
|
||||||
std::size_t location;
|
std::size_t location;
|
||||||
|
/// @brief Set of valves already opened
|
||||||
Valves valves;
|
Valves valves;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,38 +170,46 @@ auto Routes(
|
||||||
std::size_t const start,
|
std::size_t const start,
|
||||||
std::uint64_t const initial_time,
|
std::uint64_t const initial_time,
|
||||||
std::vector<Room> const& rooms,
|
std::vector<Room> const& rooms,
|
||||||
distance_array const& distances
|
distance_array<std::uint64_t> const& distances
|
||||||
) -> std::unordered_map<Valves, std::uint64_t>
|
) -> std::unordered_map<Valves, std::uint64_t>
|
||||||
{
|
{
|
||||||
std::vector<State> states { State{initial_time, 0, start, {}} };
|
// 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
|
||||||
|
std::vector<State> states { State{initial_time, 0, start, {}} };
|
||||||
while (!states.empty()) {
|
while (!states.empty()) {
|
||||||
auto const state = states.back();
|
auto const state = states.back();
|
||||||
states.pop_back();
|
states.pop_back();
|
||||||
auto const i = state.location;
|
|
||||||
|
|
||||||
for (auto [j, room] : rooms | boost::adaptors::indexed()) {
|
if (auto & best = result[state.valves]; best < state.flow) {
|
||||||
|
best = state.flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const distances_i = distances[state.location];
|
||||||
|
|
||||||
|
for (auto const j : interesting_rooms) {
|
||||||
// don't revisit a valve
|
// don't revisit a valve
|
||||||
if (state.valves.test(j)) { continue; }
|
if (state.valves.test(j)) { continue; }
|
||||||
|
|
||||||
// don't visit rooms with useless valves
|
|
||||||
if (room.flow == 0) { continue; }
|
|
||||||
|
|
||||||
// don't visit rooms we can't get to in time
|
// don't visit rooms we can't get to in time
|
||||||
auto const cost = distances[i][j];
|
// +1 accounts for the cost of actually turning the valve
|
||||||
if (cost+1 >= state.time) { continue; }
|
auto const cost = distances_i[j] + 1;
|
||||||
|
if (cost >= state.time) { continue; }
|
||||||
|
|
||||||
auto const time = state.time - cost - 1;
|
auto const time = state.time - cost;
|
||||||
auto const flow = state.flow + room.flow * time;
|
auto const flow = state.flow + rooms[j].flow * time;
|
||||||
auto valves = state.valves;
|
auto valves = state.valves;
|
||||||
valves.set(j);
|
valves.set(j);
|
||||||
|
|
||||||
// remember the best we've seen for this valve-set
|
|
||||||
if (result[valves] < flow) {
|
|
||||||
result[valves] = flow;
|
|
||||||
}
|
|
||||||
|
|
||||||
states.push_back({time, flow, static_cast<std::size_t>(j), valves});
|
states.push_back({time, flow, static_cast<std::size_t>(j), valves});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +225,7 @@ auto Routes(
|
||||||
auto Part1(
|
auto Part1(
|
||||||
std::size_t const start,
|
std::size_t const start,
|
||||||
std::vector<Room> const& rooms,
|
std::vector<Room> const& rooms,
|
||||||
distance_array 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, 30, rooms, distances);
|
||||||
|
@ -198,26 +240,27 @@ auto Part1(
|
||||||
auto Part2(
|
auto Part2(
|
||||||
std::size_t const start,
|
std::size_t const start,
|
||||||
std::vector<Room> const& rooms,
|
std::vector<Room> const& rooms,
|
||||||
distance_array 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, 26, rooms, distances);
|
||||||
auto const end = routes.end();
|
auto const end = routes.end();
|
||||||
std::uint64_t result {0};
|
|
||||||
|
std::uint64_t best {0};
|
||||||
for (auto it1 = routes.begin(); it1 != end; std::advance(it1, 1)) {
|
for (auto it1 = routes.begin(); it1 != end; std::advance(it1, 1)) {
|
||||||
for (auto it2 = std::next(it1); it2 != end; std::advance(it2, 1)) {
|
for (auto it2 = std::next(it1); it2 != end; std::advance(it2, 1)) {
|
||||||
// only consider pairs that have disjoint sets of valves
|
// only consider pairs that have disjoint sets of valves
|
||||||
if ((it1->first & it2->first).none()) {
|
if ((it1->first & it2->first).none()) {
|
||||||
result = std::max(result, it1->second + it2->second);
|
best = std::max(best, it1->second + it2->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_SUITE("2022-16 examples") {
|
TEST_SUITE("2022-16") {
|
||||||
TEST_CASE("example") {
|
TEST_CASE("example") {
|
||||||
std::istringstream in {
|
std::istringstream in {
|
||||||
R"(Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
|
R"(Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
|
||||||
|
@ -236,8 +279,48 @@ Valve JJ has flow rate=21; tunnel leads to valve II
|
||||||
CHECK(1651 == Part1(start, rooms, distances));
|
CHECK(1651 == Part1(start, rooms, distances));
|
||||||
CHECK(1707 == Part2(start, rooms, distances));
|
CHECK(1707 == Part2(start, rooms, distances));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("shortest path") {
|
||||||
|
distance_array<int> distances{boost::extents[4][4]};
|
||||||
|
std::fill_n(distances.data(), distances.num_elements(), 100);
|
||||||
|
|
||||||
|
distances[0][2] = -2;
|
||||||
|
distances[0][0] = 0;
|
||||||
|
distances[1][0] = 4;
|
||||||
|
distances[1][1] = 0;
|
||||||
|
distances[1][2] = 3;
|
||||||
|
distances[2][2] = 0;
|
||||||
|
distances[2][3] = 2;
|
||||||
|
distances[3][1] = -1;
|
||||||
|
distances[3][3] = 0;
|
||||||
|
ShortestDistances(distances);
|
||||||
|
|
||||||
|
CHECK(distances[0][0] == 0);
|
||||||
|
CHECK(distances[0][1] == -1);
|
||||||
|
CHECK(distances[0][2] == -2);
|
||||||
|
CHECK(distances[0][3] == 0);
|
||||||
|
|
||||||
|
CHECK(distances[1][0] == 4);
|
||||||
|
CHECK(distances[1][1] == 0);
|
||||||
|
CHECK(distances[1][2] == 2);
|
||||||
|
CHECK(distances[1][3] == 4);
|
||||||
|
|
||||||
|
CHECK(distances[2][0] == 5);
|
||||||
|
CHECK(distances[2][1] == 1);
|
||||||
|
CHECK(distances[2][2] == 0);
|
||||||
|
CHECK(distances[2][3] == 2);
|
||||||
|
|
||||||
|
CHECK(distances[3][0] == 3);
|
||||||
|
CHECK(distances[3][1] == -1);
|
||||||
|
CHECK(distances[3][2] == 1);
|
||||||
|
CHECK(distances[3][3] == 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Select input source and print solution to part 1 and 2
|
||||||
|
/// @param argc Command line argument count
|
||||||
|
/// @param argv Command line arguments
|
||||||
|
/// @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 const rooms = Parse(*aocpp::Startup(argc, argv));
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef AOCPP_STARTUP_HPP_
|
#ifndef AOCPP_STARTUP_HPP_
|
||||||
#define AOCPP_STARTUP_HPP_
|
#define AOCPP_STARTUP_HPP_
|
||||||
|
|
||||||
|
/// @file Startup.hpp
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -8,6 +10,9 @@
|
||||||
|
|
||||||
namespace aocpp {
|
namespace aocpp {
|
||||||
|
|
||||||
|
/// @brief Return the selected input stream or run the test suite
|
||||||
|
/// @param argc Number of arguments
|
||||||
|
/// @param argv Command line arguments
|
||||||
auto Startup(int argc, char ** argv) -> std::unique_ptr<std::istream, void(*)(std::istream*)>;
|
auto Startup(int argc, char ** argv) -> std::unique_ptr<std::istream, void(*)(std::istream*)>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user