1
1
#include " common_headers.hpp"
2
2
#include " utils/numeric_algorithm.hpp"
3
3
4
- #include < sstream>
4
+ #include < chrono>
5
+ #include < concepts>
5
6
#include < limits>
6
- #include < cmath>
7
+ #include < functional>
8
+ #include < sstream>
7
9
10
+ constexpr std::string_view gTestDataShort {
11
+ #include " ../data/2024/day7_short.txt"
12
+ };
13
+
14
+ constexpr std::string_view gInput {
15
+ #include " ../data/2024/day7_constexpr.txt"
16
+ };
8
17
9
- struct Equation final
18
+ constexpr size_t gArgumentsSizeMax {100 };
19
+ using ArgumentsArray = std::array<int64_t , gArgumentsSizeMax >;
20
+ struct Equation
10
21
{
11
22
int64_t result{};
12
- std::vector<int64_t > values;
23
+ size_t argumentsCount{};
24
+ ArgumentsArray arguments;
13
25
};
14
26
15
- [[nodiscard]] constexpr int64_t cat (int64_t a, int64_t b) noexcept {
27
+ template <typename T>
28
+ concept IsEquation = std::is_same_v<T, Equation>;
29
+
30
+ template <typename T>
31
+ concept EquationArrayContainer = requires (T a)
32
+ {
33
+ requires IsEquation<std::iter_value_t <decltype (std::begin (a))>>;
34
+ requires std::forward_iterator<decltype (std::begin (a))>;
35
+ requires std::forward_iterator<decltype (std::end (a))>;
36
+ };
37
+
38
+ template <typename T>
39
+ concept EquationPointerArray = std::is_pointer_v<T> && IsEquation<std::remove_pointer_t <T>>;
40
+
41
+ template <typename T>
42
+ concept EquationBoundedArray = std::is_bounded_array_v<T> && requires (T a)
43
+ {
44
+ requires IsEquation<std::remove_cvref_t <decltype (a[0 ])>>;
45
+ };
46
+
47
+ template <typename T>
48
+ concept EquationArray = EquationArrayContainer<T> || EquationPointerArray<T> || EquationBoundedArray<T>;
49
+
50
+
51
+ // TODO: replace the function with std::from_chairs, that becomes constexpr in C++23.
52
+ [[nodiscard]] constexpr std::pair<int64_t , size_t > parseNumber (std::string_view str) noexcept {
53
+ constexpr auto char_to_int = [](char c) {
54
+ return c - ' 0' ;
55
+ };
56
+
57
+ size_t index{};
58
+ int64_t result{};
59
+ while (index < str.size () && std::isdigit (str[index])) {
60
+ result = result * 10 + char_to_int (str[index]);
61
+ ++index;
62
+ };
63
+ return std::pair{result, index};
64
+ }
65
+
66
+ constexpr void skipNonDigits (std::string_view& str) noexcept {
67
+ const auto it = std::find_if (str.begin (), str.end (), [](const char ch) {
68
+ return std::isdigit (ch);
69
+ });
70
+
71
+ if (it != str.end ()) {
72
+ str.remove_prefix (std::distance (str.begin (), it));
73
+ }
74
+ else {
75
+ str = {};
76
+ }
77
+ }
78
+
79
+ [[nodiscard]] constexpr std::pair<ArgumentsArray, size_t > parseArgumentsEquation (std::string_view& data)
80
+ {
81
+ size_t id{};
82
+ ArgumentsArray arguments{};
83
+ while (id < gArgumentsSizeMax && !data.empty () && data.front () != ' \n ' ) {
84
+ skipNonDigits (data);
85
+ if (data.empty ()) break ;
86
+
87
+ const auto [argument, parsedNums] = parseNumber (data);
88
+
89
+ arguments[id++] = argument;
90
+ data.remove_prefix (parsedNums);
91
+ }
92
+ if (id == gArgumentsSizeMax ) {
93
+ throw std::logic_error{" Number of equation arguments exceeds the threshold" };
94
+ }
95
+
96
+ return std::pair{arguments, id};
97
+ }
98
+
99
+ [[nodiscard]] constexpr size_t numOfEquastions (std::string_view testData) noexcept
100
+ {
101
+ return std::accumulate (testData.begin (), testData.end (), size_t {}, [](size_t value, char ch) {
102
+ return value + (ch == ' :' );
103
+ });
104
+ }
105
+
106
+ [[nodiscard]] constexpr Equation parseEquation (std::string_view& testData) {
107
+ // Equestion pattern looks like: "1234: 32 12 12".
108
+ // The first number is result followed by comma.
109
+ // The rest numbers are arguments separated by whitespace.
110
+ const auto [result, parsedNums] = parseNumber (testData);
111
+ if (parsedNums + 2 >= testData.size () || testData[parsedNums] != ' :' ) {
112
+ throw std::runtime_error{" invalid input data" };
113
+ }
114
+ testData.remove_prefix (parsedNums + 2 );
115
+ auto [arguments, count] = parseArgumentsEquation (testData);
116
+
117
+ Equation equation{
118
+ .result = result,
119
+ .argumentsCount = count,
120
+ .arguments = arguments
121
+ };
122
+ return equation;
123
+ }
124
+
125
+ [[nodiscard]] constexpr auto parseEquations (std::string_view testData, EquationArray auto & equations) {
126
+ size_t index{};
127
+ const auto N{equations.size ()};
128
+ while (index < N && !testData.empty ()) {
129
+ skipNonDigits (testData);
130
+ if (testData.empty ()) break ;
131
+
132
+ equations[index] = parseEquation (testData);
133
+ ++index;
134
+ }
135
+ }
136
+
137
+ [[nodiscard]] constexpr int64_t cat (int64_t a, int64_t b) noexcept
138
+ {
16
139
const int multiplier = std::pow (10 , numOfDigits (b));
17
140
return a * multiplier + b;
18
141
};
19
142
20
- template <typename ...Operation>
143
+ template <typename T>
144
+ concept OperatorInvocable = std::regular_invocable<T, int64_t , int64_t >;
145
+
146
+ template <typename ...Operations>
147
+ requires (OperatorInvocable<Operations> && ...)
21
148
class Combiner final
22
149
{
23
150
public:
24
- constexpr explicit Combiner (Operation && ...operations) : m_functions {std::forward<Operation >(operations)...} {}
151
+ constexpr explicit Combiner (Operations && ...operations ) : m_operations {std::forward<Operations >(operations)...} {}
25
152
26
- [[nodiscard]] constexpr auto compute (const Equation& equation, size_t id, int64_t curValue) const {
153
+ [[nodiscard]] constexpr bool compute (const Equation& equation, size_t id, int64_t curValue) const {
27
154
if (curValue >= equation.result ) {
28
155
return curValue == equation.result ;
29
156
}
30
- if (id >= equation.values . size () ) {
157
+ if (id >= equation.argumentsCount ) {
31
158
return false ;
32
159
}
33
-
160
+
34
161
return std::apply (
35
162
[&, this ](const auto & ...op ) {
36
- return (op.operate (equation, id, curValue, *this ) || ...);
163
+ const auto arg{equation.arguments [id++]};
164
+ return ((compute (equation, id, op (curValue, arg))) || ...);
37
165
},
38
- m_functions);
166
+ m_operations);
167
+ }
168
+
169
+ [[nodiscard]] constexpr bool compute (const Equation& equation) const {
170
+ constexpr size_t id{};
171
+ constexpr size_t initValue{};
172
+ return compute (equation, id, initValue);
39
173
}
40
174
41
175
private:
42
- std::tuple<Operation ...> m_functions ;
176
+ std::tuple<Operations ...> m_operations ;
43
177
};
44
178
45
- template <typename ...Operation>
46
- [[nodiscard]] constexpr auto makeCombiner (Operation&& ...ops) {
179
+ template <typename ...Operation> requires (OperatorInvocable<Operation> && ...)
180
+ [[nodiscard]] constexpr auto makeCombiner(Operation&& ...ops) noexcept {
47
181
return Combiner<Operation...>{std::forward<Operation>(ops)...};
48
182
}
49
183
50
184
class CatOperation final
51
185
{
52
186
public:
53
- template <typename C>
54
- constexpr auto operate (const Equation& equation, size_t id, int64_t curValue, C context) const noexcept {
55
- return context.compute (equation, id + 1 , cat (curValue, equation.values [id]));
56
- }
57
- };
58
-
59
- class AddOperation final
60
- {
61
- public:
62
- template <typename C>
63
- constexpr auto operate (const Equation& equation, size_t id, int64_t curValue, C context) const noexcept {
64
- return context.compute (equation, id + 1 , curValue + equation.values [id]);
65
- }
66
- };
67
-
68
- class MultOperation final
69
- {
70
- public:
71
- template <typename C>
72
- constexpr auto operate (const Equation& equation, size_t id, int64_t curValue, C context) const noexcept {
73
- return context.compute (equation, id + 1 , curValue * equation.values [id]);
187
+ constexpr auto operator ()(int64_t initValue, int64_t curValue) const noexcept {
188
+ return cat (initValue, curValue);
74
189
}
75
190
};
76
191
77
- [[nodiscard]] constexpr bool canBeCombinedBy2Operations (const Equation& equation, size_t id, int64_t curValue ) {
78
- auto combiner{makeCombiner (AddOperation {}, MultOperation {})};
79
- return combiner.compute (equation, id, curValue );
192
+ [[nodiscard]] constexpr bool canBeCombinedBy2Operations (const Equation& equation) {
193
+ constexpr auto combiner{makeCombiner (std::multiplies<> {}, std::plus<> {})};
194
+ return combiner.compute (equation);
80
195
}
81
196
82
- [[nodiscard]] constexpr bool canBeCombinedBy3Operations (const Equation& equation, size_t id, int64_t curValue ) {
83
- auto combiner{makeCombiner (CatOperation {}, AddOperation {}, MultOperation {})};
84
- return combiner.compute (equation, id, curValue );
197
+ [[nodiscard]] constexpr bool canBeCombinedBy3Operations (const Equation& equation) {
198
+ auto combiner{makeCombiner (std::plus<> {}, std::multiplies<> {}, CatOperation {})};
199
+ return combiner.compute (equation);
85
200
}
86
201
87
202
template <typename CombineFun>
88
- [[nodiscard]] size_t solveImpl (const std::vector<Equation> & equations, CombineFun canBeCombined) noexcept
203
+ [[nodiscard]] constexpr size_t solveImpl (const EquationArray auto & equations, const CombineFun& canBeCombined) noexcept
89
204
{
90
- size_t totalSum{};
91
- for (const auto & equation : equations) {
92
- if (canBeCombined (equation, 0 , 0 )) {
93
- totalSum += equation.result ;
94
- }
95
- }
96
- return totalSum;
205
+ return std::accumulate (std::begin (equations), std::end (equations), size_t {}, [&canBeCombined](size_t value, const Equation& equation) {
206
+ return canBeCombined (equation) ? value + equation.result : value;
207
+ });
97
208
}
98
209
99
- [[nodiscard]] size_t solveFirstPart (const std::vector<Equation> & equations) noexcept
210
+ [[nodiscard]] constexpr size_t solveFirstPart (const EquationArray auto & equations) noexcept
100
211
{
101
212
return solveImpl (equations, canBeCombinedBy2Operations);
102
213
}
103
214
104
- [[nodiscard]] size_t solveSecondPart (const std::vector<Equation> & equations) noexcept
215
+ [[nodiscard]] constexpr size_t solveSecondPart (const EquationArray auto & equations) noexcept
105
216
{
106
217
return solveImpl (equations, canBeCombinedBy3Operations);
107
218
}
108
219
220
+ [[nodiscard]] std::string readFile (std::string_view filename) {
221
+ std::ifstream ifile (filename.data ());
222
+ if (!ifile) {
223
+ throw std::runtime_error{" Failed to open file" };
224
+ }
225
+ std::string str (std::istreambuf_iterator<char >{ifile}, {});
226
+ return str;
227
+ }
228
+
109
229
void printHelp ()
110
230
{
111
231
std::cerr << " \n Usage:\n "
@@ -121,38 +241,48 @@ int main(int argc, char* argv[])
121
241
}
122
242
123
243
std::string_view task{argv[1 ]};
124
- if (task != " part1" && task != " part2" ) {
125
- std::cerr << " \n first arg can be either `part1` or `part2`\n " ;
126
- printHelp ();
127
- return 1 ;
128
- }
129
244
130
- std::ifstream ifile (argv[2 ]);
131
- if (!ifile) {
132
- std::cerr << " \n File cannot be open" ;
133
- return 1 ;
134
- }
245
+ if (task == " part1" || task == " part2" ) {
246
+ using SolutionImplFunction = size_t (*)(const std::vector<Equation>&);
247
+ std::unordered_map<std::string_view, SolutionImplFunction> handlers{
248
+ {" part1" , solveFirstPart<std::vector<Equation>>},
249
+ {" part2" , solveSecondPart<std::vector<Equation>>},
250
+ };
135
251
136
- std::vector<Equation> equations;
137
- std::string line;
138
- while (std::getline (ifile, line)) {
139
- equations.emplace_back ();
140
- Equation& eq = equations.back ();
141
-
142
- std::istringstream ss (line);
143
- char colon{};
144
- ss >> eq.result >> colon;
145
- int num2{};
146
- while ((ss >> num2)) {
147
- eq.values .push_back (num2);
148
- }
252
+ const auto start = std::chrono::high_resolution_clock::now ();
253
+ const auto fileContent{readFile (argv[2 ])};
254
+ std::string_view fileContentView{fileContent};
255
+ const size_t N{numOfEquastions (fileContentView)};
256
+ std::vector<Equation> equations (N);
257
+ parseEquations (fileContentView, equations);
258
+
259
+ const auto res = handlers.at (task)(equations);
260
+ const auto end = std::chrono::high_resolution_clock::now ();
261
+
262
+ std::cout << res
263
+ << " elapsed " << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count ();
149
264
}
265
+ else if (task == " part1_constexpr" ) {
266
+ const auto constexpr_context = [] {
267
+ constexpr size_t N{numOfEquastions (gInput )};
268
+ std::array<Equation, N> equations{};
269
+ parseEquations (gInput , equations);
150
270
151
- if (task == " part1" ) {
152
- std::cout << solveFirstPart (equations);
271
+ return equations;
272
+ };
273
+
274
+ constexpr auto equations{constexpr_context ()};
275
+ constexpr auto firstPartAns{solveFirstPart (equations)};
276
+ std::cout << firstPartAns;
277
+
278
+ // TODO: solving second part takes a lot of time and consumes lots of memory.
279
+ // constexpr auto secondPartAns{solveSecondPart(equations)};
280
+ // std::cout << "\n" << secondPartAns;
153
281
}
154
282
else {
155
- std::cout << solveSecondPart (equations);
283
+ std::cerr << " \n first arg can be either `part1`, `part2`, or `part1_constexpr`\n " ;
284
+ printHelp ();
285
+ return 1 ;
156
286
}
157
287
158
288
return 0 ;
0 commit comments