Skip to content

Commit fb24f19

Browse files
author
bt
committed
Day7 compile time
1 parent d10f2b1 commit fb24f19

File tree

2 files changed

+220
-79
lines changed

2 files changed

+220
-79
lines changed

CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.20)
22
project(advent2024 LANGUAGES CXX)
33

4-
set(CMAKE_CXX_STANDARD_REQUIRED 20)
4+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
55
add_compile_options(-Wall -Wextra -Wpedantic -O2)
66

77
function(add_challenge name)
@@ -17,6 +17,17 @@ add_challenge(day3)
1717
add_challenge(day4)
1818
add_challenge(day5)
1919
add_challenge(day7)
20+
target_compile_options(day7 PRIVATE -fconstexpr-ops-limit=3355443200)
21+
22+
# Read the content of the input file
23+
file(READ ${CMAKE_SOURCE_DIR}/data/2024/day7.txt FILE_CONTENT)
24+
25+
# Quote the content
26+
set(FILE_CONTENT "R\"(${FILE_CONTENT})\"")
27+
28+
file(WRITE ./data/2024/day7_constexpr.txt ${FILE_CONTENT})
29+
30+
2031
add_challenge(day8)
2132
add_challenge(day9)
2233
add_challenge(day10)

src/day7.cpp

Lines changed: 208 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,231 @@
11
#include "common_headers.hpp"
22
#include "utils/numeric_algorithm.hpp"
33

4-
#include <sstream>
4+
#include <chrono>
5+
#include <concepts>
56
#include <limits>
6-
#include <cmath>
7+
#include <functional>
8+
#include <sstream>
79

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+
};
817

9-
struct Equation final
18+
constexpr size_t gArgumentsSizeMax{100};
19+
using ArgumentsArray = std::array<int64_t, gArgumentsSizeMax>;
20+
struct Equation
1021
{
1122
int64_t result{};
12-
std::vector<int64_t> values;
23+
size_t argumentsCount{};
24+
ArgumentsArray arguments;
1325
};
1426

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+
{
16139
const int multiplier = std::pow(10, numOfDigits(b));
17140
return a * multiplier + b;
18141
};
19142

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> && ...)
21148
class Combiner final
22149
{
23150
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)...} {}
25152

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 {
27154
if(curValue >= equation.result) {
28155
return curValue == equation.result;
29156
}
30-
if(id >= equation.values.size()) {
157+
if(id >= equation.argumentsCount) {
31158
return false;
32159
}
33-
160+
34161
return std::apply(
35162
[&, 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))) || ...);
37165
},
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);
39173
}
40174

41175
private:
42-
std::tuple<Operation...> m_functions;
176+
std::tuple<Operations...> m_operations;
43177
};
44178

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 {
47181
return Combiner<Operation...>{std::forward<Operation>(ops)...};
48182
}
49183

50184
class CatOperation final
51185
{
52186
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);
74189
}
75190
};
76191

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);
80195
}
81196

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);
85200
}
86201

87202
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
89204
{
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+
});
97208
}
98209

99-
[[nodiscard]] size_t solveFirstPart(const std::vector<Equation>& equations) noexcept
210+
[[nodiscard]] constexpr size_t solveFirstPart(const EquationArray auto& equations) noexcept
100211
{
101212
return solveImpl(equations, canBeCombinedBy2Operations);
102213
}
103214

104-
[[nodiscard]] size_t solveSecondPart(const std::vector<Equation>& equations) noexcept
215+
[[nodiscard]] constexpr size_t solveSecondPart(const EquationArray auto& equations) noexcept
105216
{
106217
return solveImpl(equations, canBeCombinedBy3Operations);
107218
}
108219

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+
109229
void printHelp()
110230
{
111231
std::cerr << "\nUsage:\n"
@@ -121,38 +241,48 @@ int main(int argc, char* argv[])
121241
}
122242

123243
std::string_view task{argv[1]};
124-
if(task != "part1" && task != "part2") {
125-
std::cerr << "\nfirst arg can be either `part1` or `part2`\n";
126-
printHelp();
127-
return 1;
128-
}
129244

130-
std::ifstream ifile(argv[2]);
131-
if(!ifile) {
132-
std::cerr << "\nFile 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+
};
135251

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();
149264
}
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);
150270

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;
153281
}
154282
else {
155-
std::cout << solveSecondPart(equations);
283+
std::cerr << "\nfirst arg can be either `part1`, `part2`, or `part1_constexpr`\n";
284+
printHelp();
285+
return 1;
156286
}
157287

158288
return 0;

0 commit comments

Comments
 (0)