1
+ #include < algorithm>
2
+ #include < iostream>
3
+ #include < fstream>
4
+ #include < charconv>
5
+ #include < vector>
6
+ #include < stdexcept>
7
+ #include < span>
8
+
9
+ namespace {
10
+ void parseLevels (const std::string& report, std::vector<int >& out)
11
+ {
12
+ if (report.empty ()) return ;
13
+
14
+ const char *begin = &report[0 ];
15
+ const char * const end = &report[report.size ()];
16
+ std::from_chars_result result{};
17
+ int val{};
18
+ while (begin != end) {
19
+ result = std::from_chars (begin, end, val);
20
+ if (result.ec != std::errc ()) {
21
+ std::cerr << " \n report: " << report;
22
+ throw std::logic_error{" Invalid report input string" };
23
+ }
24
+ begin = std::min (result.ptr + 1 , end);
25
+ out.push_back (val);
26
+ }
27
+ }
28
+
29
+ template <typename S>
30
+ [[nodiscard]] bool safe (std::span<const int > levels, const unsigned tolerate, const S& safer) noexcept
31
+ {
32
+ unsigned violations = 0 ;
33
+ for (size_t i = 1 ; i < levels.size () && violations <= tolerate; ++i) {
34
+ if (!safer (levels[i-1 ], levels[i])) {
35
+ ++violations;
36
+
37
+ if (i + 1 == levels.size ()) break ;
38
+
39
+
40
+ // We have to figure out which value(i - 1 or i) have to be *removed.
41
+ // This results in the next index position.
42
+
43
+ // Edge cases:
44
+ // 59 62 65 64 65 <- 65(2nd) element should be removed
45
+ // 36 34 36 33 30 <- 34(2nd) element should be removed
46
+
47
+ if (safer (levels[i - 1 ], levels[i + 1 ])) {
48
+ ++i;
49
+ }
50
+ // Prev should be deleted because current and next elements are safe.
51
+ else if (safer (levels[i], levels[i + 1 ])) {
52
+ // However, we must check that current element and prevous elements build safe consequence.
53
+ if (i > 1 ) {
54
+ // 1 2 3 10 12. 2 and 10 are not safe. This results in increase in violations.
55
+ violations += !safer (levels[i - 2 ], levels[i]);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return violations <= tolerate;
61
+ }
62
+
63
+ [[nodiscard]] constexpr bool isLevelsSafe (int largelLevel, int smallerLevel) noexcept
64
+ {
65
+ const auto diff{largelLevel - smallerLevel};
66
+ return 1 <= diff && diff <= 3 ;
67
+ }
68
+
69
+ [[nodiscard]] bool isAscendingSafe (std::span<const int > levels, unsigned tolerate) noexcept
70
+ {
71
+ return safe (levels, tolerate, [](int smaller, int larger) {
72
+ return isLevelsSafe (larger, smaller);
73
+ });
74
+ }
75
+
76
+ [[nodiscard]] bool isDescendingSafe (std::span<const int > levels, unsigned tolerate) noexcept
77
+ {
78
+ return safe (levels, tolerate, [](int larger, int smaller) {
79
+ return isLevelsSafe (larger, smaller);
80
+ });
81
+ }
82
+
83
+ [[nodiscard]] bool isSafe (std::span<const int > levels, unsigned tolerateLevel) noexcept
84
+ {
85
+ return isAscendingSafe (levels, tolerateLevel) || isDescendingSafe (levels, tolerateLevel);
86
+ }
87
+
88
+ void printHelp ()
89
+ {
90
+ std::cerr << " \n Usage:\n "
91
+ << " The program requires 2 args: (part1, part2) and the path to the file."
92
+ << " \n For example, ./day2 part1 data/day2.txt" ;
93
+ }
94
+
95
+ } // < anonymous namespace
96
+
97
+ int main (int argc, char *argv[]) {
98
+ if (argc != 3 ) {
99
+ printHelp ();
100
+ return 1 ;
101
+ }
102
+ std::string_view task{argv[1 ]};
103
+ if (task != " part1" && task != " part2" ) {
104
+ std::cerr << " \n first arg can be either `part1` or `part2`\n " ;
105
+ printHelp ();
106
+ return 1 ;
107
+ }
108
+
109
+ std::ifstream ifile (argv[2 ]);
110
+ if (!ifile) {
111
+ std::cerr << " \n File cannot be open" ;
112
+ return 1 ;
113
+ }
114
+
115
+ const unsigned tolerateLevel = task == " part1" ? 0 : 1 ;
116
+
117
+ size_t count{};
118
+ std::string report;
119
+ std::vector<int > levels;
120
+ while (std::getline (ifile, report)) {
121
+ levels.clear ();
122
+ parseLevels (report, levels);
123
+ count += isSafe (levels, tolerateLevel);
124
+ }
125
+ std::cout << " \n answer: " << count;
126
+
127
+ return 0 ;
128
+ }
0 commit comments