Skip to content

Commit 89665d7

Browse files
committed
adventofcode/cc: add solution for 2022/24.
This was a fun problem, and I'm happy I came up with a "closed form" solution for calculating the positions of the blizzards, rather than having to actually move them around. Made the rest of the code a bit easier to implement as well! This is probably not a very fast solution, but it's sufficiently fast. Benchmarks from `./bazel run -c opt //adventofcode/cc/year2022:day24_benchmark`: 2023-06-17T18:33:21+01:00 Running /home/saser/.cache/bazel/_bazel_saser/06ad534583e887506ca6aa175f8ed7b5/execroot/code/bazel-out/k8-opt/bin/adventofcode/cc/year2022/day24_benchmark Run on (16 X 4679.3 MHz CPU s) CPU Caches: L1 Data 32 KiB (x16) L1 Instruction 32 KiB (x8) L2 Unified 512 KiB (x8) L3 Unified 16384 KiB (x1) Load Average: 1.06, 1.20, 0.76 ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. ----------------------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------------------- BM_Part1/day24.real.in 21318845 ns 21316569 ns 33 BM_Part2/day24.real.in 47708781 ns 47705468 ns 14
1 parent cccd019 commit 89665d7

File tree

8 files changed

+297
-0
lines changed

8 files changed

+297
-0
lines changed

adventofcode/cc/year2022/BUILD.bazel

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,43 @@ cc_aoc_benchmark(
781781
},
782782
)
783783

784+
cc_library(
785+
name = "day24",
786+
srcs = ["day24.cc"],
787+
hdrs = ["day24.h"],
788+
deps = [
789+
"//adventofcode/cc/geometry:pos",
790+
"@com_google_absl//absl/container:flat_hash_set",
791+
"@com_google_absl//absl/status",
792+
"@com_google_absl//absl/status:statusor",
793+
"@com_google_absl//absl/strings",
794+
],
795+
)
796+
797+
cc_aoc_test(
798+
name = "day24_test",
799+
library = ":day24",
800+
part1 = {
801+
"//adventofcode/data/year2022:day24.example.in": "//adventofcode/data/year2022:day24.example.part1.out",
802+
"//adventofcode/data/year2022:day24.real.in": "//adventofcode/data/year2022:day24.real.part1.out",
803+
},
804+
part2 = {
805+
"//adventofcode/data/year2022:day24.example.in": "//adventofcode/data/year2022:day24.example.part2.out",
806+
"//adventofcode/data/year2022:day24.real.in": "//adventofcode/data/year2022:day24.real.part2.out",
807+
},
808+
)
809+
810+
cc_aoc_benchmark(
811+
name = "day24_benchmark",
812+
library = ":day24",
813+
part1 = {
814+
"//adventofcode/data/year2022:day24.real.in": "//adventofcode/data/year2022:day24.real.part1.out",
815+
},
816+
part2 = {
817+
"//adventofcode/data/year2022:day24.real.in": "//adventofcode/data/year2022:day24.real.part2.out",
818+
},
819+
)
820+
784821
cc_library(
785822
name = "day25",
786823
srcs = ["day25.cc"],

adventofcode/cc/year2022/day24.cc

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#.######
2+
#>>.<^<#
3+
#.<..<<#
4+
#>v.><>#
5+
#<^v^^>#
6+
######.#
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
18
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
54
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#.########################################################################################################################
2+
#<^^<.^>v>^.^v>^v>v<<>^^>vv><^<v>>^v>.<vv^^^<^^^vv<v.v^>>^>^>^><^<>vv<^v>>^<^>vv^v.>.vv.<^..<^v>.<v.v<>v>>.v<><v<<v<><>v<#
3+
#<>.^>v>^<>^><<>vv>v<><>^<v<^v^vv.>><>vv>>vv<^<^<vv>><>vv<^<vvv><>v>>.<>..><^<.<.><<<>^v^>^.^..vv><^vvv^^><.^^^><>^v<>v<>#
4+
#<><^.<vv^^^v<>>><v><^.^>^><vvv^vv^><^><v<^<>>...<v>.^v>>v^.>^v^^>^>>v<v>>>v<vv^^^<>^<>><<^^^v<^^v^.<>.vv.>^^v^v^<>v>^v<<#
5+
#><.v^>vv^^<<><v<>^.<^<>^^>v.v<v>>>^>>v>><v.v>.>^^<^><><>>.^v^v<^<>..<<<>.>.^<v<<v><v<<>^>v<<v.v>^>v.><vv>>><^^>>>v<^^^^<#
6+
#>>.v.^v<^<^^><<v^<>.><v<>>>>>>v>v<v>><v>v><<<..>v>vv<>><^<^><>v^^>v<<<>v>.v>>.<<>>vvv^<<v<^^v^v^v>><<^vvv^^<<v^^^^v.<^v>#
7+
#<<^v^<^^>>^.v.>>v><.^<^<^<.v<v^v>>><v^><<^^<v^^vv^<<.v^^<v^><<>.<>><>v^><><v^v>^^v>><>vv>v<><^<>.<^^>>^.^.^v^^>>^<.^.^<<#
8+
#.>v.^>v<v^>.^>vvv..v>vv>><v<<v<^^><>>v<vv>v<<<<v<<^v<^^>>v<v<v^><<^>vv<vv<>>>^^v.^<.vvv>><v<>>vv>.^<^.v>^vvvv^>v<>^>>v.>#
9+
#<v<^.>>.>vv<vv^^<><<..^<vv>vv<<<<>>^v.v^vv^v>>>..vv>>^>v^<>>>.v.<vv>.v^>^>.<.v.^>.v.v<>>.vv><<.><<v>v><v>>>v<^^^>^<>v.v>#
10+
#><v^vv<^^^<^^>v^^<>.^>>^v^v^^<<vv<v^.<<>.><>><<^v^^><v^vv<v.>v<<>.vvv>v<<v^<<<^vv<.<<>><>>>^^vv^<.>^>^<>^>^<^.<<vv^^v><<#
11+
#>v.^<.^>v.><><<>>>^>>>>^.v><^^vvvvv^<.^><>^^><^<^^^><><v<^<v>v^>^.^^><v>vv.>><<<.vvvv<<<.<^v^.<v..><^^<^v^>><^^<^v>.>v<.#
12+
#>vvv.<>v.v.><<v^<>>v<v><.>^><>^>.v^>v>v><^^v<^v.v^><><>>v<^<v>><<<^v<^^^^><>.>..^>vv><>vvv^^<>vv^>><v^^.v>^>v^<<>>>vv.^>#
13+
#<<v<>v^<vv.><v.^vv.<<^<<^<^<><vv<^^>v.<^><v.v<v.v>>>vv^>^^.<v<^...>..^^<^>><^v>>vv.>.^vvv<>^^>v<.<v<^>>^^><><>v><^<^v>^>#
14+
#<>vvv^><^<<^.>v^^^><<.><.v^>^^>><vv<<>v<<.v><v^^>^>.v^>v>><v>^v^^^<v>.>^v<><><>vv^<v^^.^>^^<.>^v>.<<v<v<^>>^^<.<<<v^^v^<#
15+
#>>>^<<^v^^<v^.<>>v^^^>v^v^<.^><>^<><^v^<<<>^<v^>v>v<^v<^>>vv.v^^v<v>><vvvvvv^>><<v>.>.v^v<^v>>>>^>><v^^.^>>vv.vv>>>^^.^>#
16+
#>>>v<<.v^<^><^>v><<vv^>^.v<^<^v<.v<>.^^v>v^.v<vv>v>v.^<^v<<<^<v>>><<<^.<v.^^<v>>^v^v.^^^.>v^.v>>>v^<^^v>v^^>^.>^<.>^vvv<#
17+
#<<^>.v<<vv..^<vv^^<v^v>>v>.^v.<>>>vv>^.v<v<.v^><<v^^v.>><.v<^v.<^^>>>^v>>^.v<vv<>v>><.^^^^<vv>v^.<.vv<^>vvv>..v.<v><<v.<#
18+
#<<><v...>v^^^^v^><v^<>^^>v<.<.>v<v>^>v<>>>>.v<v^^^><>.vv^v^<^vv.^<vv^>^.vv<v>^>^>^vv<^<^<^>^<.v<<^v>>>v>><^vvvv<v>v<^>^>#
19+
#.^<><<<><.^>>.v^<><<<^<^v>^v<><v>>vv^^>v.vv^>><^<^<.vv^>>>v<>>>v^^^<^v>>^^^<v<<^>>.vv^v.><<<^vv^^<..<>^^v^v>^.^><v<vv.>>#
20+
#<<<>.^vv>^<.v>>>^^.v<^^^v^>>^vvv><^><>.v^.vv<<^vv^<v<v>v<<v<v^v>^^><^^.v^<^v>.^v<^<v>>v<.^>^v<^v^.<>^v.><.<^^<<vv^v>^<><#
21+
#>>vv<v<>^v>^^<v><v<>v<.>>>v<v>>>>.><>>vv^<>^v>.v^vvv.v>>vv>v<>^^>><v<>^v.^><>^^>^^v>v^<v>^<>^<^^.>vv^^v><<vv<<^.<^.<.>v>#
22+
#<>><<<v<>^.<v<^<^><<<<^><vv>v<<>^><<v><>^<^v>^.^>><^^<<^^>>v^^<<...>^<v>><>^^>v<^><>.^<^<^<^<^v^<v^^>vv^^v^><^<vv>v>^vv>#
23+
#<<>.v<^.v^^v<<^v>>><v^<v><<<<v>vv.<><<^^<<^<<<><^vv^.^<>>v.>>>v^>vv>^v><>><<^><<^<^<><^<>v<v^^v><>>^v><<>v.>v<>vv><^>>>>#
24+
#>>^vv<>v<>v^<>vvv>v>^v>..<<v.^>v>><>^v<>v>><^vv^v<v>vvvv><><>v<^><<vv.^^..<^><^>vv>^^.>>vv>^v^v<^vv>^<^v^<>><>^>>^<<v<v<#
25+
#<>>^>>^.><.^v.^^v.v<^><^^^v^v.^vv<.>><vv><>^.^vvv^<vv^^vv<<>>>^^^>v<^<^^v>^^v>><vv>><>><<>^.<vv>v>v<<<vvv^^v>vv^<.>>^v><#
26+
#><>>^<<^<>>v<<<<v>^<v<v<v^^v^>^.<^>vv..<vv<<<<v>v.>.<><>><<><<<.^<^<^^><vvv<>.<.<^><v^v>.^<v<v>v^^><><^>^^><<><v.<v^v.v.#
27+
########################################################################################################################.#
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
292
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
816

0 commit comments

Comments
 (0)