diff options
-rw-r--r-- | 2023/19/Makefile | 19 | ||||
-rw-r--r-- | 2023/19/resources/input_small.txt | 17 | ||||
-rw-r--r-- | 2023/19/src/part1.cpp | 125 | ||||
-rw-r--r-- | 2023/19/src/part2.cpp | 149 | ||||
-rw-r--r-- | 2023/include/util.h | 22 |
5 files changed, 331 insertions, 1 deletions
diff --git a/2023/19/Makefile b/2023/19/Makefile new file mode 100644 index 0000000..af6a294 --- /dev/null +++ b/2023/19/Makefile | |||
@@ -0,0 +1,19 @@ | |||
1 | CXXFLAGS := -std=c++17 | ||
2 | CPPFLAGS := -I../include | ||
3 | EXE := part1 part2 | ||
4 | |||
5 | .PHONY: all clean configure | ||
6 | |||
7 | all: $(EXE) | ||
8 | |||
9 | configure: | ||
10 | bear -- $(MAKE) all | ||
11 | |||
12 | %.o: %.cpp | ||
13 | $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< -o $@ | ||
14 | |||
15 | clean: | ||
16 | rm -rf $(EXE) src/*.o compile_commands.json | ||
17 | |||
18 | %: src/%.o | ||
19 | $(CXX) $^ -o $@ | ||
diff --git a/2023/19/resources/input_small.txt b/2023/19/resources/input_small.txt new file mode 100644 index 0000000..e5b5d64 --- /dev/null +++ b/2023/19/resources/input_small.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | px{a<2006:qkq,m>2090:A,rfg} | ||
2 | pv{a>1716:R,A} | ||
3 | lnx{m>1548:A,A} | ||
4 | rfg{s<537:gd,x>2440:R,A} | ||
5 | qs{s>3448:A,lnx} | ||
6 | qkq{x<1416:A,crn} | ||
7 | crn{x>2662:A,R} | ||
8 | in{s<1351:px,qqz} | ||
9 | qqz{s>2770:qs,m<1801:hdj,R} | ||
10 | gd{a>3333:R,R} | ||
11 | hdj{m>838:A,pv} | ||
12 | |||
13 | {x=787,m=2655,a=1222,s=2876} | ||
14 | {x=1679,m=44,a=2067,s=496} | ||
15 | {x=2036,m=264,a=79,s=2244} | ||
16 | {x=2461,m=1339,a=466,s=291} | ||
17 | {x=2127,m=1623,a=2188,s=1013} | ||
diff --git a/2023/19/src/part1.cpp b/2023/19/src/part1.cpp new file mode 100644 index 0000000..b63f05d --- /dev/null +++ b/2023/19/src/part1.cpp | |||
@@ -0,0 +1,125 @@ | |||
1 | #include <iostream> | ||
2 | #include <fstream> | ||
3 | #include <array> | ||
4 | #include <vector> | ||
5 | #include <unordered_map> | ||
6 | #include <functional> | ||
7 | |||
8 | #include "util.h" | ||
9 | |||
10 | using Name = std::string; | ||
11 | using Part = std::array<int,4>; | ||
12 | using Cond = std::function<bool(const Part&)>; | ||
13 | using Rule = std::pair<Cond,Name>; | ||
14 | using Workflow = std::vector<Rule>; | ||
15 | using Workflows = std::unordered_map<Name,Workflow>; | ||
16 | |||
17 | const std::unordered_map<char,int> fields{ {'x', 0}, {'m', 1}, {'a', 2}, {'s', 3} }; | ||
18 | |||
19 | std::pair<std::vector<Part>,Workflows> parse(const char* file) | ||
20 | { | ||
21 | using namespace util::separators; | ||
22 | |||
23 | Workflows wfs; | ||
24 | std::vector<Part> parts; | ||
25 | |||
26 | if (std::ifstream input{ file }; input.is_open()) | ||
27 | { | ||
28 | std::string line; | ||
29 | std::getline(input,line); | ||
30 | while (not line.empty()) | ||
31 | { | ||
32 | auto bw = line.find('{'); | ||
33 | Name name = line.substr(0, bw); | ||
34 | |||
35 | std::vector<Rule> rules; | ||
36 | line = line.substr(bw + 1, line.size() - bw - 2); | ||
37 | for (auto srule : util::split<COMMA>(std::string_view{ line })) | ||
38 | { | ||
39 | auto tokens = util::split<COLON>(srule); | ||
40 | if (tokens.size() > 1) | ||
41 | { | ||
42 | int f = fields.at(tokens[0][0]); | ||
43 | int v = std::stoi(std::string(tokens[0].substr(2))); | ||
44 | switch (tokens[0][1]) | ||
45 | { | ||
46 | case '<': | ||
47 | rules.push_back({ | ||
48 | [f,v](const Part& p) { return p[f] < v; }, | ||
49 | Name{ tokens[1] } | ||
50 | }); | ||
51 | break; | ||
52 | default: | ||
53 | rules.push_back({ | ||
54 | [f,v](const Part& p) { return p[f] > v; }, | ||
55 | Name{ tokens[1] } | ||
56 | }); | ||
57 | }; | ||
58 | } | ||
59 | else | ||
60 | { | ||
61 | rules.push_back({ | ||
62 | [](const Part& p) { return true; }, | ||
63 | Name{ tokens[0] } | ||
64 | }); | ||
65 | } | ||
66 | } | ||
67 | wfs.insert({ name, std::move(rules) }); | ||
68 | std::getline(input,line); | ||
69 | } | ||
70 | |||
71 | while (not std::getline(input,line).eof()) | ||
72 | { | ||
73 | Part part; int i{}; | ||
74 | util::accumulate<COMMA>(std::string_view{ line }.substr(1, line.size() - 2), | ||
75 | [&part,&i](std::string_view& v) | ||
76 | { | ||
77 | part[i++] = std::stoi(std::string{ v.substr(2) }); | ||
78 | } | ||
79 | ); | ||
80 | parts.push_back(std::move(part)); | ||
81 | } | ||
82 | } | ||
83 | return { std::move(parts), std::move(wfs) }; | ||
84 | } | ||
85 | |||
86 | Name apply_workflow(const Workflow& wf, const Part& p) | ||
87 | { | ||
88 | for (const auto& rule : wf) | ||
89 | { | ||
90 | auto [cond, name] = rule; | ||
91 | if (cond(p)) return name; | ||
92 | } | ||
93 | return "R"; | ||
94 | } | ||
95 | |||
96 | int value(const Part& p) | ||
97 | { | ||
98 | return p[0] + p[1] + p[2] + p[3]; | ||
99 | } | ||
100 | |||
101 | int process(const Part& p, const Workflows& wfs) | ||
102 | { | ||
103 | Name wname = "in"; | ||
104 | while (wname != "A") | ||
105 | { | ||
106 | if (wname == "R") return 0; | ||
107 | wname = apply_workflow(wfs.at(wname), p); | ||
108 | } | ||
109 | return value(p); | ||
110 | } | ||
111 | |||
112 | int main(int argc, char* argv[]) | ||
113 | { | ||
114 | int answer{}; | ||
115 | |||
116 | auto [parts, workflows] = parse(argv[1]); | ||
117 | |||
118 | for (const auto& part : parts) | ||
119 | { | ||
120 | answer += process(part, workflows); | ||
121 | } | ||
122 | |||
123 | std::cout << answer << std::endl; | ||
124 | return 0; | ||
125 | } | ||
diff --git a/2023/19/src/part2.cpp b/2023/19/src/part2.cpp new file mode 100644 index 0000000..93bfd65 --- /dev/null +++ b/2023/19/src/part2.cpp | |||
@@ -0,0 +1,149 @@ | |||
1 | #include <iostream> | ||
2 | #include <fstream> | ||
3 | #include <array> | ||
4 | #include <vector> | ||
5 | #include <unordered_map> | ||
6 | |||
7 | #include "util.h" | ||
8 | |||
9 | constexpr int FEATS = 4; | ||
10 | constexpr int MIN = 1 - 1; | ||
11 | constexpr int MAX = 4000 + 1; | ||
12 | |||
13 | using Name = std::string; | ||
14 | using Cond = std::tuple<char,char,int>; | ||
15 | using Rule = std::pair<std::vector<Cond>,Name>; | ||
16 | using Bounds = std::array<int,2*FEATS>; | ||
17 | using Workflows = std::unordered_map<Name,Rule>; | ||
18 | |||
19 | const std::unordered_map<char,int> fields{ {'x', 0}, {'m', 2}, {'a', 4}, {'s', 6} }; | ||
20 | |||
21 | Cond neg(const Cond& cond) | ||
22 | { | ||
23 | auto [f,o,v] = cond; | ||
24 | return { f, (o == '<') ? '>' : '<', v + ((o == '<') ? -1 : 1) }; | ||
25 | } | ||
26 | |||
27 | std::pair<std::vector<Rule>,Workflows> parse(const char* file) | ||
28 | { | ||
29 | using namespace util::separators; | ||
30 | |||
31 | Workflows workflows; | ||
32 | std::vector<Rule> arules; | ||
33 | |||
34 | if (std::ifstream input{ file }; input.is_open()) | ||
35 | { | ||
36 | std::string line; | ||
37 | std::getline(input,line); | ||
38 | while (not line.empty()) | ||
39 | { | ||
40 | auto bw = line.find('{'); | ||
41 | Name next = line.substr(0, bw); | ||
42 | |||
43 | std::vector<std::pair<Cond,Name>> conds; | ||
44 | line = line.substr(bw + 1, line.size() - bw - 2); | ||
45 | for (auto srule : util::split<COMMA>(std::string_view{ line })) | ||
46 | { | ||
47 | auto tokens = util::split<COLON>(srule); | ||
48 | if (tokens.size() > 1) | ||
49 | { | ||
50 | int val = std::stoi(std::string(tokens[0].substr(2))); | ||
51 | conds.push_back({ { tokens[0][0], tokens[0][1], val }, Name{ tokens[1] } }); | ||
52 | } | ||
53 | else | ||
54 | { | ||
55 | conds.push_back({ { 0, 0, 0 }, Name{ tokens[0] } }); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | for (int i = 0; i < conds.size(); ++i) | ||
60 | { | ||
61 | auto [cond, name] = conds[i]; | ||
62 | if (name == "R") continue; | ||
63 | |||
64 | int j{ i - 1 }; | ||
65 | auto [f,o,v] = cond; | ||
66 | std::vector<Cond> nconds; | ||
67 | nconds.push_back(f ? cond : neg(std::get<0>(conds[j--]))); | ||
68 | for (; j >= 0; --j) | ||
69 | { | ||
70 | nconds.push_back(neg(std::get<0>(conds[j]))); | ||
71 | } | ||
72 | |||
73 | if (name == "A") | ||
74 | { | ||
75 | arules.push_back({ std::move(nconds), next }); | ||
76 | } | ||
77 | else | ||
78 | { | ||
79 | workflows.insert({ name, { std::move(nconds), next } }); | ||
80 | } | ||
81 | } | ||
82 | std::getline(input,line); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | return { std::move(arules), std::move(workflows) }; | ||
87 | } | ||
88 | |||
89 | Bounds operator&&(const Bounds& a, const Bounds& b) | ||
90 | { | ||
91 | Bounds c; | ||
92 | for (int i = 0; i < FEATS; ++i) | ||
93 | { | ||
94 | c[2*i] = std::max(a[2*i], b[2*i]); | ||
95 | c[2*i+1] = std::min(a[2*i+1], b[2*i+1]); | ||
96 | } | ||
97 | return c; | ||
98 | } | ||
99 | |||
100 | void operator+=(Bounds& b, const Cond& c) | ||
101 | { | ||
102 | auto [f,o,v] = c; | ||
103 | int i{ fields.at(f) }; | ||
104 | if (o == '>') | ||
105 | { | ||
106 | b[i] = std::max(b[i],v); | ||
107 | } | ||
108 | else | ||
109 | { | ||
110 | b[i + 1] = std::min(b[i + 1],v); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | Bounds compute_bounds(const Workflows& wfs, const Rule& rule) | ||
115 | { | ||
116 | Bounds bounds{ MIN, MAX, MIN, MAX, MIN, MAX, MIN, MAX }; | ||
117 | auto [conds, next] = rule; | ||
118 | for (const auto& cond : conds) | ||
119 | { | ||
120 | bounds += cond; | ||
121 | } | ||
122 | if (next == "in") return { bounds }; | ||
123 | return bounds && compute_bounds(wfs, wfs.at(next)); | ||
124 | } | ||
125 | |||
126 | long long combinations(const Bounds& bounds) | ||
127 | { | ||
128 | long long res{ 1 }; | ||
129 | for (int i = 0; i < FEATS; ++i) | ||
130 | { | ||
131 | if (bounds[2*i] >= bounds[2*i+1]) return 0; | ||
132 | res *= bounds[2*i+1] - bounds[2*i] - 1; | ||
133 | } | ||
134 | return res; | ||
135 | } | ||
136 | |||
137 | int main(int argc, char* argv[]) | ||
138 | { | ||
139 | long answer{}; | ||
140 | |||
141 | auto [arules, workflows] = parse(argv[1]); | ||
142 | for (const auto& rule : arules) | ||
143 | { | ||
144 | answer += combinations(compute_bounds(workflows, rule)); | ||
145 | } | ||
146 | |||
147 | std::cout << answer<< std::endl; | ||
148 | return 0; | ||
149 | } | ||
diff --git a/2023/include/util.h b/2023/include/util.h index 3152719..100c9ed 100644 --- a/2023/include/util.h +++ b/2023/include/util.h | |||
@@ -7,6 +7,7 @@ | |||
7 | #include <string> | 7 | #include <string> |
8 | #include <string_view> | 8 | #include <string_view> |
9 | #include <type_traits> | 9 | #include <type_traits> |
10 | #include <vector> | ||
10 | 11 | ||
11 | namespace util | 12 | namespace util |
12 | { | 13 | { |
@@ -49,7 +50,7 @@ inline void trim(std::string_view& view) | |||
49 | * @param the callable object. | 50 | * @param the callable object. |
50 | */ | 51 | */ |
51 | template<const char* delim, typename Accumulate> | 52 | template<const char* delim, typename Accumulate> |
52 | void accumulate(std::string_view& view, Accumulate acc) | 53 | void accumulate(std::string_view view, Accumulate acc) |
53 | { | 54 | { |
54 | size_t from{}, to; | 55 | size_t from{}, to; |
55 | std::string elem; | 56 | std::string elem; |
@@ -68,6 +69,25 @@ void accumulate(std::string_view& view, Accumulate acc) | |||
68 | } | 69 | } |
69 | } | 70 | } |
70 | 71 | ||
72 | /** @brief Split a string view according to a delimiter. | ||
73 | * | ||
74 | * Specialized version of `util::accumulate` collecting the split | ||
75 | * `std::string_view` in a `std::vector`. | ||
76 | * | ||
77 | * @tparam delim the delimiter to split the string view. | ||
78 | * @param view the string view. | ||
79 | * | ||
80 | * @see util::accumulate; | ||
81 | */ | ||
82 | template<const char* delim> | ||
83 | std::vector<std::string_view> split(std::string_view view) | ||
84 | { | ||
85 | std::vector<std::string_view> v; | ||
86 | auto callback = [&v](const std::string_view& s) { v.push_back(s); }; | ||
87 | accumulate<delim>(view, callback); | ||
88 | return v; | ||
89 | } | ||
90 | |||
71 | /** @brief Discard element from stream by type. | 91 | /** @brief Discard element from stream by type. |
72 | * | 92 | * |
73 | * @tparam T the type of the element to discard. | 93 | * @tparam T the type of the element to discard. |