Skip to content

Commit 096e645

Browse files
committed
2024 day 16: solve part 1
1 parent 186754c commit 096e645

File tree

8 files changed

+236
-0
lines changed

8 files changed

+236
-0
lines changed

2024/answer_tests/day16/example1.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Day 16:
2+
7036

2024/answer_tests/day16/example2.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Day 16:
2+
11048

2024/answer_tests/day16/example3.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Day 16:
2+
12

2024/input/day16/example1.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
###############
2+
#.......#....E#
3+
#.#.###.#.###.#
4+
#.....#.#...#.#
5+
#.###.#####.#.#
6+
#.#.#.......#.#
7+
#.#.#####.###.#
8+
#...........#.#
9+
###.#.#####.#.#
10+
#...#.....#.#.#
11+
#.#.#.###.#.#.#
12+
#.....#...#.#.#
13+
#.###.#.#.#.#.#
14+
#S..#.....#...#
15+
###############

2024/input/day16/example2.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################
2+
#...#...#...#..E#
3+
#.#.#.#.#.#.#.#.#
4+
#.#.#.#...#...#.#
5+
#.#.#.#.###.#.#.#
6+
#...#.#.#.....#.#
7+
#.#.#.#.#.#####.#
8+
#.#...#.#.#.....#
9+
#.#.#####.#.###.#
10+
#.#.#.......#...#
11+
#.#.###.#####.###
12+
#.#.#...#.....#.#
13+
#.#.#.#####.###.#
14+
#.#.#.........#.#
15+
#.#.#.#########.#
16+
#S#.............#
17+
#################

2024/input/day16/example3.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
###############
2+
#S...........E#
3+
###############

2024/src/day16.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/******************************************************************************
2+
* File: day16.cpp
3+
*
4+
* Author: Eric T. Johnson (yut23)
5+
* Created: 2024-12-16
6+
*****************************************************************************/
7+
8+
#include "day16.hpp"
9+
#include "lib.hpp"
10+
#include <fstream> // for ifstream
11+
#include <iostream> // for cout
12+
13+
int main(int argc, char **argv) {
14+
auto args = aoc::parse_args(argc, argv);
15+
16+
auto maze = aoc::day16::Maze::read(args.infile);
17+
18+
int distance = maze.find_shortest_path();
19+
std::cout << distance << "\n";
20+
if (args.input_type == aoc::InputType::MAIN) {
21+
assert(distance < 76404);
22+
}
23+
24+
return 0;
25+
}

2024/src/day16.hpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/******************************************************************************
2+
* File: day16.hpp
3+
*
4+
* Author: Eric T. Johnson (yut23)
5+
* Created: 2024-12-16
6+
*****************************************************************************/
7+
8+
// based on 2023 day 17 (../../2023/src/day17.hpp)
9+
10+
#ifndef DAY16_HPP_WM3C4GBV
11+
#define DAY16_HPP_WM3C4GBV
12+
13+
#include "ds/grid.hpp" // for Grid
14+
#include "graph_traversal.hpp" // for dijkstra
15+
#include "lib.hpp" // for Pos, Delta, AbsDirection, RelDirection, DEBUG, read_lines
16+
#include "util/hash.hpp" // for make_hash
17+
18+
#include <cassert> // for assert
19+
#include <compare> // for strong_ordering
20+
#include <cstddef> // for size_t
21+
#include <functional> // for bind_front, hash
22+
#include <iostream> // for istream, ostream, cerr
23+
#include <string> // for string
24+
#include <unordered_map> // for unordered_map
25+
#include <utility> // for move
26+
#include <vector> // for vector
27+
28+
namespace aoc::day16 {
29+
30+
class Maze {
31+
public:
32+
struct Key {
33+
Pos pos;
34+
AbsDirection dir;
35+
36+
std::strong_ordering operator<=>(const Key &) const = default;
37+
};
38+
39+
private:
40+
aoc::ds::Grid<char> grid;
41+
Pos start_pos;
42+
Pos end_pos;
43+
44+
void process_neighbors(const Key &key, auto &&visit) const;
45+
int get_distance(const Key &from, const Key &to) const;
46+
47+
public:
48+
int find_shortest_path() const;
49+
void print(std::ostream &, const std::vector<Key> &path = {}) const;
50+
51+
explicit Maze(std::vector<std::string> &&grid_);
52+
static Maze read(std::istream &is) { return Maze{aoc::read_lines(is)}; }
53+
};
54+
55+
std::ostream &operator<<(std::ostream &os, const Maze::Key &key) {
56+
os << "{" << key.pos << ", " << key.dir << "}";
57+
return os;
58+
}
59+
60+
void Maze::process_neighbors(const Key &key, auto &&visit) const {
61+
// try moving straight
62+
Key neighbor{key.pos + Delta(key.dir, true), key.dir};
63+
if (grid.in_bounds(neighbor.pos) && grid[neighbor.pos] != '#') {
64+
visit(neighbor);
65+
}
66+
// try turning
67+
visit(Key{key.pos, directions::turn(key.dir, RelDirection::left)});
68+
visit(Key{key.pos, directions::turn(key.dir, RelDirection::right)});
69+
}
70+
71+
int Maze::get_distance(const Key &from, const Key &to) const {
72+
if (from.dir == to.dir) {
73+
// moved forward
74+
return 1;
75+
} else {
76+
// turned 90 degrees
77+
return 1000;
78+
}
79+
}
80+
81+
int Maze::find_shortest_path() const {
82+
const auto is_target = [&end_pos = end_pos](const Key &key) -> bool {
83+
return key.pos == end_pos;
84+
};
85+
86+
const auto &[distance, path] = aoc::graph::dijkstra(
87+
Key{start_pos, AbsDirection::east},
88+
[this](const Key &key, auto &&visit_neighbor) {
89+
this->process_neighbors(key, visit_neighbor);
90+
},
91+
std::bind_front(&Maze::get_distance, this), is_target);
92+
93+
if constexpr (aoc::DEBUG) {
94+
if (distance >= 0) {
95+
std::cerr << "found path with distance " << distance << ", length "
96+
<< path.size() << ":\n";
97+
} else {
98+
std::cerr << "no path found from " << start_pos << " to " << end_pos
99+
<< "\n";
100+
}
101+
for (const Key &key : path) {
102+
std::cerr << " " << key << "\n";
103+
}
104+
std::cerr << "\n";
105+
print(std::cerr, path);
106+
std::cerr << "\n";
107+
}
108+
109+
return distance;
110+
}
111+
112+
void Maze::print(std::ostream &os, const std::vector<Key> &path) const {
113+
std::unordered_map<Pos, const Key *> path_lookup;
114+
for (const Key &key : path) {
115+
path_lookup[key.pos] = &key;
116+
}
117+
118+
Pos pos;
119+
for (pos.y = 0; pos.y < grid.height; ++pos.y) {
120+
for (pos.x = 0; pos.x < grid.width; ++pos.x) {
121+
auto it = path_lookup.find(pos);
122+
if (it != path_lookup.end()) {
123+
switch (it->second->dir) {
124+
case AbsDirection::north:
125+
os << '^';
126+
break;
127+
case AbsDirection::east:
128+
os << '>';
129+
break;
130+
case AbsDirection::south:
131+
os << 'v';
132+
break;
133+
case AbsDirection::west:
134+
os << '<';
135+
break;
136+
}
137+
} else {
138+
os << grid[pos];
139+
}
140+
}
141+
os << '\n';
142+
}
143+
}
144+
145+
Maze::Maze(std::vector<std::string> &&grid_)
146+
: grid(std::move(grid_)), start_pos(-1, -1), end_pos(-1, -1) {
147+
grid.for_each_with_pos([this](const Pos &pos, char value) {
148+
if (value == 'S') {
149+
this->start_pos = pos;
150+
} else if (value == 'E') {
151+
this->end_pos = pos;
152+
}
153+
});
154+
assert(start_pos.x != -1 && start_pos.y != -1);
155+
assert(end_pos.x != -1 && end_pos.y != -1);
156+
}
157+
158+
} // namespace aoc::day16
159+
160+
template <>
161+
struct std::hash<aoc::day16::Maze::Key> {
162+
std::size_t operator()(const aoc::day16::Maze::Key &key) const noexcept {
163+
// random number (hexdump -n8 -e '"0x" 8/1 "%02x" "ull\n"'</dev/urandom)
164+
std::size_t seed = 0x2b139d1412006c1dull;
165+
util::make_hash(seed, key.pos.x, key.pos.y, key.dir);
166+
return seed;
167+
}
168+
};
169+
170+
#endif /* end of include guard: DAY16_HPP_WM3C4GBV */

0 commit comments

Comments
 (0)