1+ #include < string>
2+ #include < unordered_map>
3+ #include < numeric>
4+ #include < array>
5+ #include < queue>
6+ #include " ../aocio/aocio.hpp"
7+
8+ /*
9+ Problem: https://adventofcode.com/2023/day/10
10+
11+ Solutions:
12+ - Part 1: 6812
13+ - Part 2:
14+ Notes:
15+
16+ */
17+
18+ struct GridPos {
19+ int x, y;
20+ };
21+
22+ enum class Direction {North=0 , East=1 , South=2 , West=3 };
23+ const std::array<Direction,4 > all_dirs = {Direction::North, Direction::East, Direction::South, Direction::West};
24+
25+ const std::unordered_map<char , std::pair<Direction, Direction>> pipe_to_dir {
26+ {' |' , {Direction::North, Direction::South}},
27+ {' -' , {Direction::East, Direction::West}},
28+ {' L' , {Direction::North, Direction::East}},
29+ {' J' , {Direction::North, Direction::West}},
30+ {' 7' , {Direction::South, Direction::West}},
31+ {' F' , {Direction::South, Direction::East}},
32+ };
33+
34+ Direction dir_opposite (Direction d)
35+ {
36+ switch (d) {
37+ case Direction::North:
38+ return Direction::South;
39+ case Direction::South:
40+ return Direction::North;
41+ case Direction::East:
42+ return Direction::West;
43+ case Direction::West:
44+ return Direction::East;
45+ default :
46+ throw " Invalid direction" ;
47+ }
48+ }
49+
50+ bool pipes_connect (char p1, char p2, Direction p1_rel_dir)
51+ {
52+ if (p1 == ' S' && p2 == ' S' ) {
53+ throw " Two start vertices" ;
54+ }
55+ if (p1 == ' .' || p2 == ' .' ) {
56+ return false ;
57+ }
58+
59+ std::optional<std::pair<Direction, Direction>> p1_dir {};
60+ std::optional<std::pair<Direction, Direction>> p2_dir {};
61+ if (pipe_to_dir.contains (p1)) {
62+ p1_dir = pipe_to_dir.at (p1);
63+ } else if (p1 != ' S' ) {
64+ throw " Invalid pipe symbol" ;
65+ }
66+
67+ if (pipe_to_dir.contains (p2)) {
68+ p2_dir = pipe_to_dir.at (p2);
69+ } else if (p2 != ' S' ) {
70+ throw " Invalid pipe symbol" ;
71+ }
72+
73+ if (!p1_dir && !p2_dir) {
74+ throw " Invalid pipe symbol" ;
75+ }
76+
77+ if (p2_dir) {
78+ if (p2_dir.value ().first != p1_rel_dir && p2_dir.value ().second != p1_rel_dir) {
79+ return false ;
80+ }
81+ } else {
82+ assert (p1_dir);
83+ if (p1_dir.value ().first != dir_opposite (p1_rel_dir) && p1_dir.value ().second != dir_opposite (p1_rel_dir)) {
84+ return false ;
85+ }
86+ }
87+
88+ if (p1 == ' S' || p2 == ' S' ) {
89+ return true ;
90+ }
91+
92+ if (!p1_dir || !p2_dir) {
93+ throw " Should not happen" ;
94+ }
95+
96+ return dir_opposite (p1_dir.value ().first ) == (p2_dir.value ().first ) || dir_opposite (p1_dir.value ().first ) == (p2_dir.value ().second ) ||
97+ dir_opposite (p1_dir.value ().second ) == (p2_dir.value ().first ) || dir_opposite (p1_dir.value ().second ) == p2_dir.value ().second ;
98+ }
99+
100+ GridPos vertname_to_gridpos (const std::string& vert)
101+ {
102+ auto sep_idx = vert.find (" ," );
103+ assert (sep_idx != std::string::npos);
104+ assert (sep_idx < vert.size () - 1 );
105+ int x = aocio::parse_num (vert.substr (0 , sep_idx));
106+ int y = aocio::parse_num (vert.substr (sep_idx + 1 , vert.size ()));
107+ return GridPos {x, y};
108+ }
109+
110+ std::string gridpos_to_vertname (const GridPos& pos)
111+ {
112+ std::string vert;
113+ vert = std::to_string (pos.x ) + " ," + std::to_string (pos.y );
114+ return vert;
115+ }
116+
117+ struct Graph {
118+ std::string start_vert {};
119+ std::vector<std::string> grid {};
120+ std::unordered_map<std::string, int > loop_verts_dist {};
121+
122+ GridPos start_pos ()
123+ {
124+ return vertname_to_gridpos (start_vert);
125+ }
126+
127+ char grid_get_sym (const GridPos& pos)
128+ {
129+ return grid.at (pos.y ).at (pos.x );
130+ }
131+
132+ bool pos_on_grid (const GridPos &pos)
133+ {
134+ bool in_y = pos.y >= 0 && pos.y < std::ssize (grid);
135+ if (!in_y) {
136+ return false ;
137+ }
138+ bool in_x = pos.x >= 0 && pos.x < std::ssize (grid.at (pos.y ));
139+ return in_x;
140+ }
141+
142+ std::optional<char > grid_peek (const GridPos &pos, Direction d, GridPos &new_pos)
143+ {
144+ GridPos peek_pos = pos;
145+ switch (d)
146+ {
147+ case Direction::North:
148+ peek_pos.y -= 1 ;
149+ break ;
150+ case Direction::East:
151+ peek_pos.x += 1 ;
152+ break ;
153+ case Direction::South:
154+ peek_pos.y += 1 ;
155+ break ;
156+ case Direction::West:
157+ peek_pos.x -= 1 ;
158+ break ;
159+ default :
160+ throw " Invalid direction" ;
161+ break ;
162+ }
163+
164+ if (pos_on_grid (peek_pos)) {
165+ new_pos = peek_pos;
166+ return grid_get_sym (peek_pos);
167+ } else {
168+ new_pos = pos;
169+ return {};
170+ }
171+ }
172+
173+ void find_adjacent (const GridPos& pos, std::vector<GridPos>& neighbors)
174+ {
175+ char grid_sym = grid_get_sym (pos);
176+ for (Direction d : all_dirs) {
177+ GridPos peek_pos;
178+ auto neighbor_sym = grid_peek (pos, d, peek_pos);
179+ if (neighbor_sym && pipe_to_dir.contains (neighbor_sym.value ()) && pipes_connect (neighbor_sym.value (), grid_sym, d)) {
180+ neighbors.push_back (peek_pos);
181+ }
182+ }
183+ assert (neighbors.size () <= 2 );
184+ }
185+
186+ int find_loop_verts ()
187+ {
188+ // Simple breadth-first-search of the cyclical graph.
189+ loop_verts_dist.insert ({start_vert, 0 });
190+ std::queue<std::string> vert_queue;
191+ vert_queue.push (start_vert);
192+
193+ while (!vert_queue.empty ()) {
194+ std::string vert = vert_queue.front ();
195+ vert_queue.pop ();
196+
197+ int dist = loop_verts_dist.at (vert);
198+
199+ std::vector<GridPos> neighbors;
200+ find_adjacent (vertname_to_gridpos (vert), neighbors);
201+ for (const auto & pos : neighbors) {
202+ std::string vert_name = gridpos_to_vertname (pos);
203+ if (loop_verts_dist.contains (vert_name)) { // Already visited.
204+ continue ;
205+ } else {
206+ loop_verts_dist.insert ({vert_name, dist + 1 });
207+ vert_queue.push (vert_name);
208+ }
209+ }
210+ }
211+ auto max_dist_it = std::max_element (loop_verts_dist.begin (), loop_verts_dist.end (), [](const auto & a, const auto & b) -> bool {return a.second < b.second ;} );
212+ if (max_dist_it == loop_verts_dist.end ()) {
213+ throw " Could not find max dist" ;
214+ }
215+ return max_dist_it->second ;
216+ }
217+ };
218+
219+ void parse_grid (const std::vector<std::string>& lines, Graph &result)
220+ {
221+ std::string start_vert {" " };
222+ int y = 0 ;
223+ for (const std::string &line : lines) {
224+ if (!line.size ()) {
225+ ++y;
226+ continue ;
227+ }
228+ auto idx = line.find (' S' );
229+ if (idx != std::string::npos) {
230+ assert (start_vert == " " );
231+ int x = static_cast <int >(idx);
232+ start_vert = gridpos_to_vertname (GridPos{x, y});
233+ }
234+ result.grid .push_back (line);
235+ ++y;
236+ }
237+ assert (start_vert != " " );
238+
239+ result.start_vert = start_vert;
240+ }
241+
242+ int part_one (const std::vector<std::string>& lines)
243+ {
244+ Graph g;
245+ parse_grid (lines, g);
246+ int max_dist = g.find_loop_verts ();
247+ return max_dist;
248+ }
249+
250+ int part_two (const std::vector<std::string>& lines)
251+ {
252+ return -1 ;
253+ }
254+
255+ int main ()
256+ {
257+ aocio::print_day ();
258+ std::vector<std::string> lines;
259+ std::string_view fname = AOC_INPUT_DIR" input.txt" ;
260+ bool file_loaded = aocio::file_getlines (fname, lines);
261+ if (!file_loaded) {
262+ std::cerr << " Error: " << " File '" << fname << " ' not found\n " ;
263+ return EXIT_FAILURE;
264+ }
265+ try {
266+ int p1 = part_one (lines);
267+ std::cout << " Part 1: " << p1 << " \n " ;
268+ int p2 = part_two (lines);
269+ std::cout << " Part 2: " << p2 << " \n " ;
270+ } catch (const char * err) {
271+ std::cerr << " Error: " << err << " \n " ;
272+ return EXIT_FAILURE;
273+ }
274+ return EXIT_SUCCESS;
275+ }
0 commit comments