|
| 1 | +#include "adventofcode/cc/year2022/day24.h" |
| 2 | + |
| 3 | +#include <cmath> |
| 4 | +#include <queue> |
| 5 | +#include <string> |
| 6 | +#include <tuple> |
| 7 | +#include <vector> |
| 8 | + |
| 9 | +#include "absl/container/flat_hash_set.h" |
| 10 | +#include "absl/status/status.h" |
| 11 | +#include "absl/status/statusor.h" |
| 12 | +#include "absl/strings/str_split.h" |
| 13 | +#include "absl/strings/string_view.h" |
| 14 | +#include "adventofcode/cc/geometry/pos.h" |
| 15 | + |
| 16 | +namespace adventofcode { |
| 17 | +namespace cc { |
| 18 | +namespace year2022 { |
| 19 | +namespace day24 { |
| 20 | +namespace { |
| 21 | +class Cave { |
| 22 | + public: |
| 23 | + Cave(absl::string_view input) { |
| 24 | + cave_ = absl::StrSplit(input, '\n', absl::SkipEmpty()); |
| 25 | + width_ = cave_[0].size() - 2; // Everything except left and right walls. |
| 26 | + height_ = cave_.size() - 2; // Everything except top and bottom walls. |
| 27 | + } |
| 28 | + |
| 29 | + adventofcode::cc::geometry::Pos Entrance() const { |
| 30 | + return adventofcode::cc::geometry::Pos{.x = 1, .y = 0}; |
| 31 | + } |
| 32 | + |
| 33 | + adventofcode::cc::geometry::Pos Exit() const { |
| 34 | + return adventofcode::cc::geometry::Pos{.x = int(width_), |
| 35 | + .y = int(height_) + 1}; |
| 36 | + } |
| 37 | + |
| 38 | + bool Occupied(const adventofcode::cc::geometry::Pos& pos, size_t time) const { |
| 39 | + return cave_[pos.y][pos.x] == '#' || // Check for walls. |
| 40 | + OccupiedUp(pos, time) || OccupiedDown(pos, time) || |
| 41 | + OccupiedLeft(pos, time) || OccupiedRight(pos, time); |
| 42 | + } |
| 43 | + |
| 44 | + size_t ShortestPath(const adventofcode::cc::geometry::Pos& src, size_t time, |
| 45 | + const adventofcode::cc::geometry::Pos& dst) const { |
| 46 | + using state = std::tuple<adventofcode::cc::geometry::Pos, |
| 47 | + size_t>; // Position and time. |
| 48 | + // h is the heuristic function. It calculates the Manhattan distance to the |
| 49 | + // destination. |
| 50 | + auto h = [&dst](const adventofcode::cc::geometry::Pos& p) -> int64_t { |
| 51 | + return p.Distance(dst); |
| 52 | + }; |
| 53 | + // less implements the ordering used by the priority queue. The queue uses a |
| 54 | + // less function to implement a priority queue where the _maximum_ element |
| 55 | + // is placed first. Therefore, to get the _minimum_ element first (which is |
| 56 | + // what we want here), we need to reverse the logic of the less function. |
| 57 | + auto less = [h](const state& s1, const state& s2) -> bool { |
| 58 | + const auto& [p1, t1] = s1; |
| 59 | + const auto& [p2, t2] = s2; |
| 60 | + |
| 61 | + return (t1 + h(p1)) > (t2 + h(p2)); |
| 62 | + }; |
| 63 | + absl::flat_hash_set<state> seen; |
| 64 | + std::priority_queue<state, std::vector<state>, decltype(less)> q(less); |
| 65 | + // Starting point: entrance of cave at time 0. |
| 66 | + q.push({src, time}); |
| 67 | + while (!q.empty()) { |
| 68 | + state s = q.top(); |
| 69 | + q.pop(); |
| 70 | + if (seen.contains(s)) { |
| 71 | + continue; |
| 72 | + } |
| 73 | + seen.insert(s); |
| 74 | + const auto& [pos, t] = s; |
| 75 | + if (pos == dst) { |
| 76 | + return t - time; |
| 77 | + } |
| 78 | + for (const adventofcode::cc::geometry::Pos& d : { |
| 79 | + adventofcode::cc::geometry::Pos{.x = 0, .y = 0}, // Wait. |
| 80 | + adventofcode::cc::geometry::Pos{.x = 0, .y = -1}, // Up. |
| 81 | + adventofcode::cc::geometry::Pos{.x = 0, .y = +1}, // Down. |
| 82 | + adventofcode::cc::geometry::Pos{.x = -1, .y = 0}, // Left. |
| 83 | + adventofcode::cc::geometry::Pos{.x = +1, .y = 0}, // Right. |
| 84 | + }) { |
| 85 | + adventofcode::cc::geometry::Pos pos2 = pos + d; |
| 86 | + size_t t2 = t + 1; |
| 87 | + if (pos2.x < 0 || pos2.x >= width_ + 1 || pos2.y < 0 || |
| 88 | + pos2.y >= height_ + 2) { |
| 89 | + // Out of bounds. |
| 90 | + continue; |
| 91 | + } |
| 92 | + if (Occupied(pos2, t2)) { |
| 93 | + // Cannot move to the new position. |
| 94 | + continue; |
| 95 | + } |
| 96 | + q.push({pos2, t2}); |
| 97 | + } |
| 98 | + } |
| 99 | + return -1; |
| 100 | + } |
| 101 | + |
| 102 | + private: |
| 103 | + // The Occupied* methods return true if a blizzard is occupying the given |
| 104 | + // position at the given time. We use a little bit of math to make them O(1) |
| 105 | + // operations. Using '<' (left) blizzards as an example, the main idea is that |
| 106 | + // a position (x, y) being occupied at time T is equivalent to position (x+1, |
| 107 | + // y) being occupied at time T-1. This induction-style argument means that we |
| 108 | + // can calculate whether (x, y) is occupied at time T by checking whether |
| 109 | + // (x+T, y) is occupied at time 0 -- and this we can look up directly in the |
| 110 | + // input. |
| 111 | + // |
| 112 | + // We use modular arithmetic to account for the fact that blizzards loop |
| 113 | + // around the walls. The width_ and height_ variables help with this. |
| 114 | + // |
| 115 | + // We need to be careful with translating between coordinate systems. The |
| 116 | + // input positions consider (0,0) to be the top left of the input -- i.e. the |
| 117 | + // wall next to the entrance. For the modular arithmetic to work, we need to |
| 118 | + // consider (0,0) to be the top left of the cave, which is (1,1) in the input |
| 119 | + // positions. That's why there is code that adds and subtracts 1 in these |
| 120 | + // functions -- to translate between these coordinate systems. |
| 121 | + |
| 122 | + bool OccupiedUp(const adventofcode::cc::geometry::Pos& pos, |
| 123 | + size_t time) const { |
| 124 | + // y is occupied at T if y+1 is occupied at T-1. |
| 125 | + // => y is occupied at T if y+T is occupied at 0. |
| 126 | + // => y is occupied at T if (y+T) mod H is occupied at 0. |
| 127 | + size_t y = pos.y - 1; // Coordinate system translation. |
| 128 | + y = (y + time) % height_; // Modulo arithmetic. |
| 129 | + y += 1; // Coordinate system translation. |
| 130 | + return cave_[y][pos.x] == '^'; |
| 131 | + } |
| 132 | + |
| 133 | + bool OccupiedDown(const adventofcode::cc::geometry::Pos& pos, |
| 134 | + size_t time) const { |
| 135 | + // y is occupied at T if y-1 is occupied at T-1. |
| 136 | + // => y is occupied at T if y-T is occupied at 0. |
| 137 | + // => y is occupied at T if (y-T) mod H is occupied at 0. |
| 138 | + // y-T can underflow since size_t is unsigned. However: |
| 139 | + // * if T' = T % H, then (y-T) mod H == (y-T') mod H. |
| 140 | + // * (y-T') can still underflow, but we can get around that by noting that |
| 141 | + // (y-T') mod H == (y-T'+H) mod H. |
| 142 | + // |
| 143 | + // All in all, we calculate y as follows: |
| 144 | + size_t y = pos.y - 1; // Coordinate system translation. |
| 145 | + time %= height_; // Make sure that 0 <= time < height_. |
| 146 | + if (y < time) { |
| 147 | + y += height_; |
| 148 | + } |
| 149 | + y = (y - time) % height_; |
| 150 | + y += 1; // Coordinate system translation. |
| 151 | + return cave_[y][pos.x] == 'v'; |
| 152 | + } |
| 153 | + |
| 154 | + bool OccupiedLeft(const adventofcode::cc::geometry::Pos& pos, |
| 155 | + size_t time) const { |
| 156 | + // x is occupied at T if x+1 is occupied at T-1. |
| 157 | + // => x is occupied at T if x+T is occupied at 0. |
| 158 | + // => x is occupied at T if (x+T) mod W is occupied at 0. |
| 159 | + size_t x = pos.x - 1; // Coordinate system translation. |
| 160 | + x = (x + time) % width_; // Modulo arithmetic. |
| 161 | + x += 1; // Coordinate system translation. |
| 162 | + return cave_[pos.y][x] == '<'; |
| 163 | + } |
| 164 | + |
| 165 | + bool OccupiedRight(const adventofcode::cc::geometry::Pos& pos, |
| 166 | + size_t time) const { |
| 167 | + // x is occupied at T if x-1 is occupied at T-1. |
| 168 | + // => x is occupied at T if x-T is occupied at 0. |
| 169 | + // => x is occupied at T if (x-T) mod W is occupied at 0. |
| 170 | + // x-T can underflow since size_t is unsigned. However: |
| 171 | + // * if T' = T % W, then (x-T) mod W == (x-T') mod W. |
| 172 | + // * (x-T') can still underflow, but we can get around that by noting that |
| 173 | + // (x-T') mod W == (x-T'+W) mod W. |
| 174 | + // |
| 175 | + // All in all, we calculate x as follows: |
| 176 | + size_t x = pos.x - 1; // Coordinate system translation. |
| 177 | + time %= width_; // Make sure that 0 <= time < width_. |
| 178 | + if (x < time) { |
| 179 | + x += width_; |
| 180 | + } |
| 181 | + x = (x - time) % width_; |
| 182 | + x += 1; // Coordinate system translation. |
| 183 | + return cave_[pos.y][x] == '>'; |
| 184 | + } |
| 185 | + |
| 186 | + // The entire input, split into lines. |
| 187 | + std::vector<absl::string_view> cave_; |
| 188 | + // The width and height of the cave, _excluding_ the walls. In other words, |
| 189 | + // this cave has a width of 2 and height of 2: |
| 190 | + // #.## |
| 191 | + // #>v# |
| 192 | + // #^<# |
| 193 | + // ##.# |
| 194 | + size_t width_, height_; |
| 195 | +}; |
| 196 | + |
| 197 | +absl::StatusOr<std::string> solve(absl::string_view input, bool part1) { |
| 198 | + Cave c(input); |
| 199 | + adventofcode::cc::geometry::Pos entrance = c.Entrance(); |
| 200 | + adventofcode::cc::geometry::Pos exit = c.Exit(); |
| 201 | + size_t time = 0; |
| 202 | + time += c.ShortestPath(entrance, time, exit); |
| 203 | + if (part1) { |
| 204 | + return std::to_string(time); |
| 205 | + } |
| 206 | + time += c.ShortestPath(exit, time, entrance); |
| 207 | + time += c.ShortestPath(entrance, time, exit); |
| 208 | + return std::to_string(time); |
| 209 | +} |
| 210 | +} // namespace |
| 211 | + |
| 212 | +absl::StatusOr<std::string> Part1(absl::string_view input) { |
| 213 | + Cave c(input); |
| 214 | + return solve(input, /*part1=*/true); |
| 215 | +} |
| 216 | + |
| 217 | +absl::StatusOr<std::string> Part2(absl::string_view input) { |
| 218 | + return solve(input, /*part1=*/false); |
| 219 | +} |
| 220 | +} // namespace day24 |
| 221 | +} // namespace year2022 |
| 222 | +} // namespace cc |
| 223 | +} // namespace adventofcode |
0 commit comments