Skip to content

Commit 3c0ed7e

Browse files
committed
adventofcode/cc: add solution for 2022/21.
Another fun problem -- I always enjoy tree traversals and simple "interpreter" problems like this one. The code isn't very pretty, but it works and runs fast enough. After some playing around, it turns out that preallocating the right number of elements in the hash map makes a difference in runtime: preallocation is ~30 % faster than no preallocation, in my benchmarks. Benchmarks from `./bazel run -c opt //adventofcode/cc/year2022:day24_benchmark`: === With preallocation === 2023-06-17T20:56:59+01:00 Running /home/saser/.cache/bazel/_bazel_saser/06ad534583e887506ca6aa175f8ed7b5/execroot/code/bazel-out/k8-opt/bin/adventofcode/cc/year2022/day21_benchmark Run on (16 X 4679.3 MHz CPU s) CPU Caches: L1 Data 32 KiB (x16) L1 Instruction 32 KiB (x8) L2 Unified 512 KiB (x8) L3 Unified 16384 KiB (x1) Load Average: 1.04, 1.10, 0.88 ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. ----------------------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------------------- BM_Part1/day21.real.in 340559 ns 340553 ns 2058 BM_Part2/day21.real.in 344647 ns 344638 ns 2033 === Without preallocation === 2023-06-17T20:43:26+01:00 Running /home/saser/.cache/bazel/_bazel_saser/06ad534583e887506ca6aa175f8ed7b5/execroot/code/bazel-out/k8-opt/bin/adventofcode/cc/year2022/day21_benchmark Run on (16 X 4679.3 MHz CPU s) CPU Caches: L1 Data 32 KiB (x16) L1 Instruction 32 KiB (x8) L2 Unified 512 KiB (x8) L3 Unified 16384 KiB (x1) Load Average: 0.55, 0.43, 0.40 ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. ----------------------------------------------------------------- Benchmark Time CPU Iterations ----------------------------------------------------------------- BM_Part1/day21.real.in 542154 ns 542147 ns 1286 BM_Part2/day21.real.in 543297 ns 543290 ns 1290
1 parent 89665d7 commit 3c0ed7e

File tree

8 files changed

+2254
-0
lines changed

8 files changed

+2254
-0
lines changed

adventofcode/cc/year2022/BUILD.bazel

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,43 @@ cc_aoc_benchmark(
743743
},
744744
)
745745

746+
cc_library(
747+
name = "day21",
748+
srcs = ["day21.cc"],
749+
hdrs = ["day21.h"],
750+
deps = [
751+
"@com_google_absl//absl/container:flat_hash_map",
752+
"@com_google_absl//absl/log:check",
753+
"@com_google_absl//absl/status",
754+
"@com_google_absl//absl/status:statusor",
755+
"@com_google_absl//absl/strings",
756+
],
757+
)
758+
759+
cc_aoc_test(
760+
name = "day21_test",
761+
library = ":day21",
762+
part1 = {
763+
"//adventofcode/data/year2022:day21.example.in": "//adventofcode/data/year2022:day21.example.part1.out",
764+
"//adventofcode/data/year2022:day21.real.in": "//adventofcode/data/year2022:day21.real.part1.out",
765+
},
766+
part2 = {
767+
"//adventofcode/data/year2022:day21.example.in": "//adventofcode/data/year2022:day21.example.part2.out",
768+
"//adventofcode/data/year2022:day21.real.in": "//adventofcode/data/year2022:day21.real.part2.out",
769+
},
770+
)
771+
772+
cc_aoc_benchmark(
773+
name = "day21_benchmark",
774+
library = ":day21",
775+
part1 = {
776+
"//adventofcode/data/year2022:day21.real.in": "//adventofcode/data/year2022:day21.real.part1.out",
777+
},
778+
part2 = {
779+
"//adventofcode/data/year2022:day21.real.in": "//adventofcode/data/year2022:day21.real.part2.out",
780+
},
781+
)
782+
746783
cc_library(
747784
name = "day23",
748785
srcs = ["day23.cc"],

adventofcode/cc/year2022/day21.cc

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#include "adventofcode/cc/year2022/day21.h"
2+
3+
#include <cstdint>
4+
#include <string>
5+
#include <variant>
6+
#include <vector>
7+
8+
#include "absl/container/flat_hash_map.h"
9+
#include "absl/log/check.h"
10+
#include "absl/status/status.h"
11+
#include "absl/status/statusor.h"
12+
#include "absl/strings/numbers.h"
13+
#include "absl/strings/str_split.h"
14+
#include "absl/strings/string_view.h"
15+
16+
namespace adventofcode {
17+
namespace cc {
18+
namespace year2022 {
19+
namespace day21 {
20+
namespace {
21+
22+
// Monkey represents a single monkey.
23+
class Monkey {
24+
public:
25+
// Const represents a constant integer.
26+
struct Constant {
27+
int64_t v;
28+
};
29+
30+
// Binary represents a binary arithmetic operation.
31+
struct Binary {
32+
std::string left;
33+
char op;
34+
std::string right;
35+
};
36+
37+
Monkey() = delete;
38+
Monkey(absl::string_view name, int64_t v)
39+
: name_(name), expr_(Constant{.v = v}) {}
40+
Monkey(absl::string_view name, absl::string_view left, char op,
41+
absl::string_view right)
42+
: name_(name),
43+
expr_(Binary{
44+
.left = std::string(left), .op = op, .right = std::string(right)}) {
45+
}
46+
47+
std::variant<Constant, Binary> Expr() const { return expr_; }
48+
49+
Binary MustBinary() const { return std::get<Binary>(expr_); }
50+
51+
private:
52+
std::string name_;
53+
54+
// expr_ is either a constant integer or a binary arithmetic expression.
55+
std::variant<Constant, Binary> expr_;
56+
};
57+
58+
absl::flat_hash_map<std::string, Monkey> Parse(absl::string_view input) {
59+
std::vector<absl::string_view> lines =
60+
absl::StrSplit(input, '\n', absl::SkipEmpty());
61+
// By preallocating the right number of elements we appear to save a bunch of
62+
// time.
63+
absl::flat_hash_map<std::string, Monkey> monkeys(lines.size());
64+
for (absl::string_view line : lines) {
65+
// This split will result in one of the two following forms:
66+
// 1. ["asbo", "12"] in case of a constant.
67+
// 2. ["bioa", "bhja", "+", "buyi"] in case of an operation.
68+
std::vector<absl::string_view> parts =
69+
absl::StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty());
70+
CHECK(parts.size() == 2 || parts.size() == 4) << line;
71+
absl::string_view name = parts[0];
72+
switch (parts.size()) {
73+
case 2:
74+
int64_t v;
75+
CHECK(absl::SimpleAtoi(parts[1], &v));
76+
monkeys.insert_or_assign(name, Monkey(name, v));
77+
break;
78+
case 4:
79+
monkeys.insert_or_assign(
80+
name, Monkey(name, /*left=*/parts[1], /*op=*/parts[2][0],
81+
/*right=*/parts[3]));
82+
break;
83+
}
84+
}
85+
return monkeys;
86+
}
87+
88+
// Value represents what a Monkey evaluates to.
89+
struct Value {
90+
int64_t value;
91+
bool has_humn;
92+
};
93+
94+
void Evaluate(const absl::flat_hash_map<std::string, Monkey>& monkeys,
95+
absl::flat_hash_map<std::string, Value>& values,
96+
absl::string_view node) {
97+
const Monkey& m = monkeys.at(node);
98+
Value v;
99+
if (std::variant<Monkey::Constant, Monkey::Binary> expr = m.Expr();
100+
std::holds_alternative<Monkey::Constant>(expr)) {
101+
Monkey::Constant c = std::get<Monkey::Constant>(expr);
102+
v.value = c.v;
103+
v.has_humn = node == "humn";
104+
} else {
105+
Monkey::Binary b = std::get<Monkey::Binary>(expr);
106+
Evaluate(monkeys, values, b.left);
107+
Value left = values.at(b.left);
108+
Evaluate(monkeys, values, b.right);
109+
Value right = values.at(b.right);
110+
switch (b.op) {
111+
case '+':
112+
v.value = left.value + right.value;
113+
break;
114+
case '-':
115+
v.value = left.value - right.value;
116+
break;
117+
case '/':
118+
v.value = left.value / right.value;
119+
break;
120+
case '*':
121+
v.value = left.value * right.value;
122+
break;
123+
}
124+
v.has_humn = left.has_humn || right.has_humn;
125+
}
126+
values[node] = v;
127+
}
128+
129+
absl::flat_hash_map<std::string, Value> Evaluate(
130+
const absl::flat_hash_map<std::string, Monkey>& monkeys,
131+
absl::string_view node) {
132+
// By preallocating the right number of elements we appear to save a bunch of
133+
// time.
134+
absl::flat_hash_map<std::string, Value> values(monkeys.size());
135+
Evaluate(monkeys, values, node);
136+
return values;
137+
}
138+
139+
int64_t FindHumn(const absl::flat_hash_map<std::string, Monkey>& monkeys,
140+
const absl::flat_hash_map<std::string, Value>& values,
141+
absl::string_view node, int64_t target) {
142+
if (node == "humn") {
143+
return target;
144+
}
145+
Monkey::Binary b = monkeys.at(node).MustBinary();
146+
const Value& left = values.at(b.left);
147+
const Value& right = values.at(b.right);
148+
std::string next_node = left.has_humn ? b.left : b.right;
149+
int64_t next_target = -1;
150+
if (left.has_humn) {
151+
// target = left (humn) <op> right
152+
// => target <reverse op> right = left (humn)
153+
int64_t r = right.value;
154+
switch (b.op) {
155+
case '+':
156+
next_target = target - r;
157+
break;
158+
case '-':
159+
next_target = target + r;
160+
break;
161+
case '/':
162+
next_target = target * r;
163+
break;
164+
case '*':
165+
next_target = target / r;
166+
break;
167+
}
168+
} else {
169+
int64_t l = left.value;
170+
switch (b.op) {
171+
case '+':
172+
// target = left + right (humn)
173+
// => target - left = right (humn)
174+
next_target = target - l;
175+
break;
176+
case '-':
177+
// target = left - right (humn)
178+
// => target + right (humn) = left
179+
// => right (humn) = left - target
180+
next_target = l - target;
181+
break;
182+
case '/':
183+
// target = left / right (humn)
184+
// => target * right (humn) = left
185+
// => right (humn) = left / target
186+
next_target = l / target;
187+
break;
188+
case '*':
189+
// target = left * right (humn)
190+
// => target / left = right (humn)
191+
next_target = target / l;
192+
break;
193+
}
194+
}
195+
return FindHumn(monkeys, values, next_node, next_target);
196+
}
197+
198+
int64_t FindHumn(const absl::flat_hash_map<std::string, Monkey>& monkeys,
199+
const absl::flat_hash_map<std::string, Value>& values) {
200+
Monkey::Binary root = monkeys.at("root").MustBinary();
201+
const Value& left = values.at(root.left);
202+
const Value& right = values.at(root.right);
203+
std::string node;
204+
int64_t target;
205+
if (!left.has_humn) {
206+
target = left.value;
207+
node = root.right;
208+
} else {
209+
target = right.value;
210+
node = root.left;
211+
}
212+
return FindHumn(monkeys, values, node, target);
213+
}
214+
215+
absl::StatusOr<std::string> solve(absl::string_view input, bool part1) {
216+
absl::flat_hash_map<std::string, Monkey> monkeys = Parse(input);
217+
absl::flat_hash_map<std::string, Value> values = Evaluate(monkeys, "root");
218+
if (part1) {
219+
return std::to_string(values.at("root").value);
220+
}
221+
return std::to_string(FindHumn(monkeys, values));
222+
}
223+
} // namespace
224+
225+
absl::StatusOr<std::string> Part1(absl::string_view input) {
226+
return solve(input, /*part1=*/true);
227+
}
228+
229+
absl::StatusOr<std::string> Part2(absl::string_view input) {
230+
return solve(input, /*part1=*/false);
231+
}
232+
} // namespace day21
233+
} // namespace year2022
234+
} // namespace cc
235+
} // namespace adventofcode
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root: pppw + sjmn
2+
dbpl: 5
3+
cczh: sllz + lgvd
4+
zczc: 2
5+
ptdq: humn - dvpt
6+
dvpt: 3
7+
lfqf: 4
8+
humn: 5
9+
ljgn: 2
10+
sjmn: drzm * dbpl
11+
sllz: 4
12+
pppw: cczh / lfqf
13+
lgvd: ljgn * ptdq
14+
drzm: hmdt - zczc
15+
hmdt: 32
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
152
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
301

0 commit comments

Comments
 (0)