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