diff --git a/2019/18.cpp b/2019/18.cpp index b4cdfbf..beb9bed 100644 --- a/2019/18.cpp +++ b/2019/18.cpp @@ -18,6 +18,8 @@ #include #include +using namespace aocpp; + // lowercase key // uppercase door // start at @ @@ -26,8 +28,8 @@ namespace { -using namespace aocpp; -using Map = std::vector; +Coord(* const directions[4])(Coord) = {Up, Down, Left, Right}; + using Features = std::map; using Doors = std::bitset<26>; using Distances = std::map>>; @@ -36,22 +38,29 @@ auto SetKey(Doors& doors, char key) { return doors.set(std::toupper(key) - 'A'); } -auto ReadMap(std::istream & in) -> Map { - Map result; - std::string line; - while (std::getline(in, line)) { - result.emplace_back(std::move(line)); +struct Map { + std::vector rows; + + auto operator[](Coord c) -> char& { return rows[c.y][c.x]; } + auto operator[](Coord c) const -> char { return rows[c.y][c.x]; } + + static auto Parse(std::istream & in) -> Map { + Map result; + std::string line; + while (std::getline(in, line)) { + result.rows.emplace_back(std::move(line)); + } + return {result}; } - return result; -} +}; auto FindFeatures(Map const& map) -> Features { - std::map features; - for (std::int64_t y = 0; y < map.size(); y++) { - for (std::int64_t x = 0; x < map[y].size(); x++) { - auto const c = map[y][x]; + Features features; + for (std::int64_t y = 0; y < map.rows.size(); y++) { + for (std::int64_t x = 0; x < map.rows[y].size(); x++) { + auto const c = map[{x,y}]; if ('#' != c && '.' != c) { - features[c] = Coord{x,y}; + features[c] = {x,y}; } } } @@ -59,15 +68,15 @@ auto FindFeatures(Map const& map) -> Features { } auto FindDistancesFrom( - Map const& map, Features const& features, + Map const& map, + Features const& features, Distances & distances, - char start_letter, - Coord start + char const start_letter, + Coord const start ) { std::map> result; - std::deque> todo - {{0, start, {}}}; + std::deque> todo {{0, start, {}}}; std::set seen; @@ -77,7 +86,7 @@ auto FindDistancesFrom( // Don't visit the same coordinate twice if (!seen.insert(here).second) continue; - auto const c = map[here.y][here.x]; + auto const c = map[here]; if (c == '#') continue; // avoid walls // success, we've found a key, record the path @@ -92,7 +101,7 @@ auto FindDistancesFrom( } // Visit all neighbors - for (auto && fn : {Up,Down,Left,Right}) { + for (auto const fn : directions) { todo.emplace_back(steps+1, fn(here), doors); } } @@ -100,15 +109,13 @@ auto FindDistancesFrom( return result; } -auto FindDistances(Map const& map, Features const& features) -> Distances { +auto FindDistances(Map const& map, Features const& features) { Distances distances; - - for (auto && [start_letter, start_coord] : features) { + for (auto const [start_letter, start_coord] : features) { if (!std::isupper(start_letter)) { FindDistancesFrom(map, features, distances, start_letter, start_coord); } } - return distances; } @@ -121,11 +128,12 @@ auto SolveMaze( using Visited = std::pair; std::set seen; - using QElt = std::tuple; - struct Cmp { auto operator()(QElt const& x, QElt const& y) -> bool { - return std::get<0>(x) > std::get<0>(y); - }}; - std::priority_queue, Cmp> todo; + // Priority queue returning lowest path cost states first. + using PqElt = std::tuple; + using PqCmp = decltype([](PqElt const& x, PqElt const& y) { + return std::get<0>(x) > std::get<0>(y); }); + std::priority_queue, PqCmp> todo; + todo.emplace(0, initial_locations, Doors()); while(!todo.empty()) { @@ -138,10 +146,10 @@ auto SolveMaze( if (seen.emplace(locations, keys.to_ullong()).second) { for (auto& location : locations) { - auto save = location; + auto const save = location; - for (auto [next, costneed] : distances.at(location)) { - auto [cost, need] = costneed; + for (auto const [next, costneed] : distances.at(location)) { + auto const [cost, need] = costneed; if ((need & ~keys).none()) { // no missing keys location = next; auto keys_ = keys; SetKey(keys_, next); @@ -156,30 +164,28 @@ auto SolveMaze( throw std::runtime_error{"no solution to part 1"}; } -auto UpdateMap(Map& map, Features & features) { - auto start = features['@']; - map[start.y][start.x] = '#'; - map[start.y][start.x+1] = '#'; - map[start.y][start.x-1] = '#'; - map[start.y+1][start.x] = '#'; - map[start.y-1][start.x] = '#'; +// Part 2 instructs us to update the map splitting it into 4 quadrants +auto Part2(Map& map, Features & features) { + auto const start = features['@']; + for (auto const fn : directions) { + map[fn(start)] = '#'; + } features.erase('@'); - features['^'] = Up(Left(start)); - features['&'] = Down(Left(start)); - features['*'] = Up(Right(start)); + features['^'] = Up(Left (start)); + features['&'] = Down(Left (start)); + features['*'] = Up(Right(start)); features['$'] = Down(Right(start)); } } // namespace auto main(int argc, char** argv) -> int { - auto map = ReadMap(Startup(argc, argv)); + auto map = Map::Parse(Startup(argc, argv)); auto features = FindFeatures(map); auto distances = FindDistances(map, features); - std::cout << "Part 1: " << SolveMaze(distances, "@") << std::endl; - - UpdateMap(map, features); + + Part2(map, features); distances = FindDistances(map, features); std::cout << "Part 2: " << SolveMaze(distances, "^&*$") << std::endl; }