This commit is contained in:
Eric Mertens 2022-11-10 17:46:30 -08:00
parent 5f080c1903
commit 6865e3594e

View File

@ -32,6 +32,10 @@ using Features = std::map<char, Coord>;
using Doors = std::bitset<26>; using Doors = std::bitset<26>;
using Distances = std::map<char,std::map<char, std::pair<std::int64_t, Doors>>>; using Distances = std::map<char,std::map<char, std::pair<std::int64_t, Doors>>>;
auto SetKey(Doors& doors, char key) {
return doors.set(std::toupper(key) - 'A');
}
auto ReadMap(std::istream & in) -> Map { auto ReadMap(std::istream & in) -> Map {
Map result; Map result;
std::string line; std::string line;
@ -45,11 +49,9 @@ auto FindFeatures(Map const& map) -> Features {
std::map<char,Coord> features; std::map<char,Coord> features;
for (std::int64_t y = 0; y < map.size(); y++) { for (std::int64_t y = 0; y < map.size(); y++) {
for (std::int64_t x = 0; x < map[y].size(); x++) { for (std::int64_t x = 0; x < map[y].size(); x++) {
auto c = map[y][x]; auto const c = map[y][x];
switch (c) { if ('#' != c && '.' != c) {
default: features[c] = Coord{x,y}; features[c] = Coord{x,y};
case '#':
case '.': {}
} }
} }
} }
@ -59,32 +61,39 @@ auto FindFeatures(Map const& map) -> Features {
auto FindDistancesFrom( auto FindDistancesFrom(
Map const& map, Features const& features, Map const& map, Features const& features,
Distances & distances, Distances & distances,
char start_letter, Coord start char start_letter,
Coord start
) { ) {
std::map<char, std::pair<std::int64_t, Doors>> result; std::map<char, std::pair<std::int64_t, Doors>> result;
std::set<Coord> seen;
std::deque<std::tuple<std::int64_t, Coord, Doors>> todo std::deque<std::tuple<std::int64_t, Coord, Doors>> todo
{{0, start, {}}}; {{0, start, {}}};
while (!todo.empty()) { std::set<Coord> seen;
for (; !todo.empty(); todo.pop_front()) {
auto [steps, here, doors] = todo.front(); auto [steps, here, doors] = todo.front();
todo.pop_front();
if (seen.insert(here).second) { // Don't visit the same coordinate twice
auto c = map[here.y][here.x]; if (!seen.insert(here).second) continue;
if (c == '#') {
continue; auto const c = map[here.y][here.x];
} if (c == '#') continue; // avoid walls
if (c != start_letter && std::islower(c)) {
// success, we've found a key, record the path
if (c != start_letter && std::islower(c)) {
distances[start_letter][c] = {steps, doors}; distances[start_letter][c] = {steps, doors};
continue; continue; // don't walk beyond the key
} }
if (std::isupper(c)) {
doors.set(c - 'A'); // Note any keys we encounter on our journey
} if (std::isupper(c)) {
for (auto && fn : {Up,Down,Left,Right}) { SetKey(doors, c);
auto here_ = fn(here); }
todo.push_back({steps+1, here_, doors});
} // Visit all neighbors
for (auto && fn : {Up,Down,Left,Right}) {
todo.emplace_back(steps+1, fn(here), doors);
} }
} }
@ -103,53 +112,41 @@ auto FindDistances(Map const& map, Features const& features) -> Distances {
return distances; return distances;
} }
auto HasKey(Doors doors, char key) {
return doors.test(std::toupper(key) - 'A');
}
auto SetKey(Doors doors, char key) {
return doors.set(std::toupper(key) - 'A');
}
template <class Elt, class Compare>
auto MakePQueue(Compare const& compare)
-> std::priority_queue<Elt, std::vector<Elt>, Compare>
{
return {compare};
}
auto SolveMaze( auto SolveMaze(
Distances const& distances, Distances const& distances,
std::set<char> const initial_locations std::string const initial_locations
) -> std::int64_t ) -> std::int64_t
{ {
// Track current positions and current set of keys in easy to compare form // Track current positions and current set of keys in easy to compare form
using Visited = std::pair<std::set<char>, unsigned long long>; using Visited = std::pair<std::string, unsigned long long>;
std::set<Visited> seen; std::set<Visited> seen;
auto todo = MakePQueue<std::tuple<std::int64_t, std::set<char>, Doors>>( using QElt = std::tuple<std::int64_t, std::string, Doors>;
[](auto const& x, auto const& y) { struct Cmp { auto operator()(QElt const& x, QElt const& y) -> bool {
return std::get<0>(x) > std::get<0>(y); return std::get<0>(x) > std::get<0>(y);
} }};
); std::priority_queue<QElt, std::vector<QElt>, Cmp> todo;
todo.emplace(0, initial_locations, Doors()); todo.emplace(0, initial_locations, Doors());
while (!todo.empty()) { while(!todo.empty()) {
auto [steps, locations, keys] = todo.top(); auto [steps, locations, keys] = todo.top();
todo.pop(); todo.pop();
if (keys.all()) { return steps; } if (keys.all()) { return steps; }
if (seen.insert({locations,keys.to_ullong()}).second) { std::sort(locations.begin(), locations.end());
for (auto location : locations) { if (seen.emplace(locations, keys.to_ullong()).second) {
auto locations1 = locations; locations1.erase(location);
for (auto && [next, costneed] : distances.at(location)) { for (auto& location : locations) {
auto save = location;
for (auto [next, costneed] : distances.at(location)) {
auto [cost, need] = costneed; auto [cost, need] = costneed;
if ((need & ~keys).none()) { if ((need & ~keys).none()) { // no missing keys
auto locations2 = locations1; locations2.insert(next); location = next;
todo.emplace(steps + cost, locations2, SetKey(keys, next)); auto keys_ = keys; SetKey(keys_, next);
todo.emplace(steps + cost, locations, keys_);
location = save;
} }
} }
} }
@ -180,9 +177,9 @@ auto main(int argc, char** argv) -> int {
auto features = FindFeatures(map); auto features = FindFeatures(map);
auto distances = FindDistances(map, features); auto distances = FindDistances(map, features);
std::cout << "Part 1: " << SolveMaze(distances, {'@'}) << std::endl; std::cout << "Part 1: " << SolveMaze(distances, "@") << std::endl;
UpdateMap(map, features); UpdateMap(map, features);
distances = FindDistances(map, features); distances = FindDistances(map, features);
std::cout << "Part 2: " << SolveMaze(distances, {'^', '&', '*', '$'}) << std::endl; std::cout << "Part 2: " << SolveMaze(distances, "^&*$") << std::endl;
} }