Skip to content

Commit 3d7bc98

Browse files
committed
Completed Day 6
1 parent 7f178b3 commit 3d7bc98

File tree

7 files changed

+459
-22
lines changed

7 files changed

+459
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The solutions have been created in the C++ programming language as a library.
77
| | ||||||
88
|-|-|-|-|-|-|-|
99
||1|2|3|4|5|6|
10-
||:x: |:x:|:x:|:x:|:x:| |
10+
||:x: |:x:|:x:|:x:|:x:|:x:|
1111
|7|8|9|10|11|12|13|
1212
| | | | | | | | |
1313
|14|15|16|17|18|19|20|

include/advent_of_code/day_6.hxx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,37 @@
33

44
#include <fstream>
55
#include <string>
6+
#include <vector>
7+
#include <filesystem>
8+
#include <unordered_map>
9+
#include <iostream>
10+
#include <algorithm>
611

712
#include "spdlog/spdlog.h"
813

914
namespace AdventOfCode24::Day6 {
1015
typedef std::pair<int, int> coord;
11-
typedef std::pair<int, int> direction;
12-
typedef std::pair<coord,direction> status;
13-
std::pair<coord, std::vector<coord>> get_area_map(const std::filesystem::path& input_file);
16+
17+
class Map {
18+
public:
19+
coord size{-1, -1};
20+
coord guard_position{-1,-1};
21+
coord guard_direction{0, 0};
22+
std::vector<coord> obstacles;
23+
Map(const coord& size, const coord& guard_position, const coord& guard_direction, const std::vector<coord>& obstacles) :
24+
size(size), guard_position(guard_position), guard_direction(guard_direction), obstacles(obstacles) {}
25+
Map(){};
26+
Map(const Map& other) : size(other.size), guard_position(other.guard_position), guard_direction(other.guard_direction), obstacles(other.obstacles) {}
27+
};
28+
29+
enum class ExitStatus {
30+
ExitedMap,
31+
LoopDetected,
32+
};
33+
34+
Map get_area_map(const std::filesystem::path& input_file);
35+
void show_result(const Map& map, const std::vector<coord>&path={});
36+
coord new_direction(const coord& current_direction);
37+
std::pair<ExitStatus, std::vector<coord>> get_path(const Map& map);
38+
std::vector<coord> patrol_loop_obstruction_positions(const Map& map);
1439
};

solutions/data/day_6.txt

Lines changed: 130 additions & 0 deletions
Large diffs are not rendered by default.

solutions/day_6.cxx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <filesystem>
2+
#include <numeric>
3+
#include <string>
4+
#include <set>
5+
#include "spdlog/common.h"
6+
#include "spdlog/spdlog.h"
7+
8+
#include "advent_of_code/day_6.hxx"
9+
10+
using namespace AdventOfCode24::Day6;
11+
12+
int main(int argc, char** argv) {
13+
if(argc != 2) {
14+
spdlog::error("Expected input data file!");
15+
return 1;
16+
}
17+
18+
spdlog::set_level(spdlog::level::info);
19+
20+
const std::filesystem::path input_file{argv[1]};
21+
if(!std::filesystem::exists(input_file)) {
22+
throw std::runtime_error("File " + input_file.string() + " does not exist!");
23+
}
24+
25+
const Map map{get_area_map(input_file)};
26+
27+
const std::pair<ExitStatus, std::vector<coord>> path_check{get_path(map)};
28+
const std::set<coord> unique_elem(path_check.second.begin(), path_check.second.end());
29+
const std::vector<coord> optimum_obstacle_pos{patrol_loop_obstruction_positions(map)};
30+
31+
spdlog::info("For file '" + input_file.string() + "' the number of unique coordinates visited by the guard is " + std::to_string(unique_elem.size()));
32+
spdlog::info("The number of locations in which an obstacle can be placed to cause the guard to loop is " + std::to_string(optimum_obstacle_pos.size()));
33+
34+
show_result(map, path_check.second);
35+
}

src/day_6.cxx

Lines changed: 184 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,194 @@
11
#include "advent_of_code/day_6.hxx"
2+
#include "spdlog/spdlog.h"
3+
#include <algorithm>
4+
#include <iterator>
5+
#include <stdexcept>
6+
#include <string>
7+
#include <unordered_map>
28

39
namespace AdventOfCode24::Day6 {
4-
std::pair<status, std::vector<coord>> get_area_map(const std::filesystem::path& input_file) {
5-
6-
std::ifstream read_in(input_file, std::ios::in);
7-
std::string line;
8-
std::vector<coord> area_map;
9-
status guard_init{{-1,-1},{0,0}};
10-
const char guard_symbols[4]{'v','>','<','^'};
11-
12-
13-
while (std::getline(read_in, line)) {
14-
char symbol{'@'};
15-
std::istringstream iss;
16-
iss.str(line);
17-
int column{0}
18-
19-
while(iss >> symbol) {
10+
coord new_direction(const coord& current_direction) {
11+
if(current_direction.first > 0) {
12+
return {0, -1};
13+
}
14+
if(current_direction.first < 0) {
15+
return {0, 1};
16+
}
17+
if(current_direction.second > 0) {
18+
return {1, 0};
19+
}
20+
if(current_direction.second < 0) {
21+
return {-1, 0};
22+
}
23+
24+
throw std::runtime_error("Direction change failed for direction " + std::to_string(current_direction.first) + "," + std::to_string(current_direction.second));
25+
}
26+
27+
Map get_area_map(const std::filesystem::path& input_file) {
28+
std::ifstream read_in(input_file, std::ios::in);
29+
std::string line;
30+
Map area_map;
31+
32+
const std::unordered_map<char, coord> directions{
33+
{'^', {-1, 0}},
34+
{'v', {1, 0}},
35+
{'>', {0, 1}},
36+
{'<', {0, -1}}
37+
};
38+
39+
int line_num{0};
40+
41+
while (std::getline(read_in, line)) {
42+
int column{0};
43+
area_map.size.second = line.size();
44+
45+
for(const char& symbol : line) {
46+
if(symbol == '.') {
47+
column++;
48+
continue;
49+
}
50+
51+
// Firstly check if this is an obstruction location
2052
if(symbol == '#') {
21-
area_map.push_back({area_map.size(), column});
53+
spdlog::debug("Found obstacle at [" + std::to_string(line_num) + "," + std::to_string(column) + "]");
54+
area_map.obstacles.push_back({line_num, column});
2255
column++;
2356
continue;
2457
}
25-
auto find_guard = std::find(std::begin(guard_symbols), std::end(guard_symbols), symbol);
58+
59+
// Secondly check if this location is where the guard starts
60+
auto find_guard = directions.find(symbol);
61+
62+
if(find_guard != directions.end()) {
63+
const coord start_dir{directions.at(symbol)};
64+
std::string sym_str{symbol};
65+
area_map.guard_direction = start_dir;
66+
area_map.guard_position = {line_num, column};
67+
spdlog::debug("Found Guard at [" + std::to_string(line_num) + "," + std::to_string(column) + "]");
68+
spdlog::debug("Guard facing direction '" + sym_str + "'");
69+
}
70+
column++;
71+
}
72+
73+
line_num++;
74+
}
75+
76+
area_map.size.first = line_num;
77+
78+
return area_map;
79+
}
80+
81+
void show_result(const Map& map, const std::vector<coord>&path) {
82+
for(int row{0}; row < map.size.first; ++row) {
83+
for(int column{0}; column < map.size.second; ++column) {
84+
const coord current{row, column};
85+
auto find_obstacle = std::find(map.obstacles.begin(), map.obstacles.end(), current);
86+
auto find_path = std::find(path.begin(), path.end(), current);
87+
88+
if(row == map.obstacles[map.obstacles.size() - 1].first && column == map.obstacles[map.obstacles.size() - 1].second) {
89+
std::cout << "\x1B[34m■\033[0m";
90+
} else if(find_obstacle != map.obstacles.end()) {
91+
std::cout << "\x1B[31m■\033[0m";
92+
} else if(find_path != path.end()) {
93+
std::cout << "\x1B[32m■\033[0m";
94+
} else {
95+
std::cout << "";
96+
}
2697
}
98+
99+
std::cout << std::endl;
100+
}
101+
std::cout << std::endl;
102+
}
103+
104+
std::pair<ExitStatus, std::vector<coord>> get_path(const Map& map) {
105+
spdlog::debug("Guard starting at " + std::to_string(map.guard_position.first) + "," + std::to_string(map.guard_position.second));
106+
coord current_pos{map.guard_position};
107+
coord current_dir{map.guard_direction};
108+
std::vector<coord> path{current_pos};
109+
const int HARD_LIMIT{map.size.first * map.size.second};
110+
111+
while(true) {
112+
const coord next_coord{
113+
current_pos.first + current_dir.first,
114+
current_pos.second + current_dir.second
115+
};
116+
117+
const coord next_coord_2{
118+
next_coord.first + current_dir.first,
119+
next_coord.second + current_dir.second
120+
};
121+
122+
auto find_obstacle = std::find(map.obstacles.begin(), map.obstacles.end(), next_coord);
123+
124+
auto find_next = std::find(path.begin(), path.end(), next_coord);
125+
auto find_next_2 = std::find(path.begin(), path.end(), next_coord_2);
126+
127+
if(find_next != path.end() && find_next_2 != path.end()) {
128+
const int next_distance = std::distance(path.begin(), find_next);
129+
const int next_2_distance = std::distance(path.begin(), find_next_2);
130+
131+
if(next_2_distance - next_distance == 1) {
132+
spdlog::warn("Loop detected aborting path.");
133+
return {ExitStatus::LoopDetected, path};
134+
}
135+
}
136+
137+
// Need to prevent infinite looping, if path size is comparable to grid size this is likely a loop
138+
if(path.size() > HARD_LIMIT) {
139+
spdlog::warn("Path size is unusually large, assuming loop detected and aborting path.");
140+
return {ExitStatus::LoopDetected, path};
141+
}
142+
143+
// Check if an obstacle was encountered
144+
if(find_obstacle != map.obstacles.end()) {
145+
spdlog::debug("Changing direction at " + std::to_string(current_pos.first) + "," + std::to_string(current_pos.second));
146+
current_dir = new_direction(current_dir);
147+
} else if(
148+
current_pos.first < 0 || current_pos.first >= map.size.first ||
149+
current_pos.second < 0 || current_pos.second >= map.size.second
150+
) {
151+
spdlog::debug("Guard exiting map region at " + std::to_string(current_pos.first) + "," + std::to_string(current_pos.second));
152+
path.pop_back();
153+
break;
154+
} else {
155+
current_pos = next_coord;
156+
path.push_back(current_pos);
157+
}
158+
}
159+
160+
return {ExitStatus::ExitedMap, path};
161+
}
162+
std::vector<coord> patrol_loop_obstruction_positions(const Map& map) {
163+
std::vector<coord> loopback_obs_positions;
164+
165+
for(int row{0}; row < map.size.first; ++row) {
166+
spdlog::info("Process is " + std::to_string(1.0 * row / map.size.first * 100.0) + "% complete");
167+
for(int column{0}; column < map.size.second; ++column) {
168+
const int total_cells{map.size.first * map.size.second};
169+
spdlog::debug("Placing obstacle at " + std::to_string(row) + "," + std::to_string(column));
170+
// Exclude guard position
171+
if(column == map.guard_position.second && row == map.guard_position.first) continue;
172+
173+
const coord current{row, column};
174+
175+
// Exclude positions of current obstacles
176+
auto find_existing = std::find(map.obstacles.begin(), map.obstacles.end(), current);
177+
178+
if(find_existing != map.obstacles.end()) continue;
179+
180+
Map modified_map(map);
181+
modified_map.obstacles.push_back({row, column});
182+
183+
const std::pair<ExitStatus, std::vector<coord>> path_check{get_path(modified_map)};
184+
185+
if(path_check.first == ExitStatus::LoopDetected) {
186+
show_result(modified_map, path_check.second);
187+
loopback_obs_positions.push_back(current);
188+
}
189+
}
190+
}
191+
192+
return loopback_obs_positions;
27193
}
28194
};

tests/data/day_6_1.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
....#.....
2+
.........#
3+
..........
4+
..#.......
5+
.......#..
6+
..........
7+
.#..^.....
8+
........#.
9+
#.........
10+
......#...

tests/test_day_6.cxx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <gtest/gtest.h>
2+
#include "advent_of_code/day_6.hxx"
3+
#include "spdlog/spdlog.h"
4+
5+
using namespace AdventOfCode24::Day6;
6+
7+
#ifndef ADVENT_OF_CODE_DATA
8+
#error "ADVENT_OF_CODE_DATA is not defined!"
9+
#endif
10+
11+
TEST(TestAOC, TestDay6pt1_create_map) {
12+
const Map expected(
13+
{10, 10},
14+
{6, 4},
15+
{-1, 0},
16+
{
17+
{0, 4}, {1, 9},
18+
{3, 2}, {4, 7},
19+
{6, 1}, {7, 8},
20+
{8, 0}, {9, 6}
21+
}
22+
);
23+
24+
const std::filesystem::path input_file = std::filesystem::path(ADVENT_OF_CODE_DATA) / "day_6_1.txt";
25+
26+
const Map map{get_area_map(input_file)};
27+
28+
ASSERT_EQ(map.guard_direction, expected.guard_direction);
29+
ASSERT_EQ(map.guard_direction, expected.guard_direction);
30+
ASSERT_EQ(map.obstacles, expected.obstacles);
31+
ASSERT_EQ(map.size, expected.size);
32+
33+
show_result(map);
34+
}
35+
36+
TEST(TestAOC, TestDay6pt1_new_direction) {
37+
const coord original{0, 1};
38+
coord expected{1, 0};
39+
coord new_coord = new_direction(original);
40+
ASSERT_EQ(new_coord, expected);
41+
new_coord = new_direction(new_coord);
42+
expected = {0, -1};
43+
ASSERT_EQ(new_coord, expected);
44+
new_coord = new_direction(new_coord);
45+
expected = {-1, 0};
46+
ASSERT_EQ(new_coord, expected);
47+
new_coord = new_direction(new_coord);
48+
expected = {0, 1};
49+
ASSERT_EQ(new_coord, expected);
50+
}
51+
52+
TEST(TestAOC, TestDay6pt1_example) {
53+
const std::filesystem::path input_file = std::filesystem::path(ADVENT_OF_CODE_DATA) / "day_6_1.txt";
54+
const Map map{get_area_map(input_file)};
55+
56+
const std::pair<ExitStatus, std::vector<coord>> path_check{get_path(map)};
57+
const std::set<coord> unique_elem(path_check.second.begin(), path_check.second.end());
58+
59+
show_result(map, path_check.second);
60+
61+
ASSERT_EQ(unique_elem.size(), 41);
62+
}
63+
64+
TEST(TestAOC, TestDay6pt2_example) {
65+
const std::filesystem::path input_file = std::filesystem::path(ADVENT_OF_CODE_DATA) / "day_6_1.txt";
66+
const Map map{get_area_map(input_file)};
67+
68+
const std::vector<coord> loopback_obs_positions{patrol_loop_obstruction_positions(map)};
69+
70+
ASSERT_EQ(loopback_obs_positions.size(), 6);
71+
}

0 commit comments

Comments
 (0)