Skip to content

Commit 78005f1

Browse files
committed
Solve day 19 of AoC 2018
1 parent cf7e378 commit 78005f1

File tree

5 files changed

+436
-0
lines changed

5 files changed

+436
-0
lines changed

2018/aoc18/BUILD

+26
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,29 @@ cc_binary(
255255
":day16",
256256
],
257257
)
258+
259+
cc_library(
260+
name = "day19",
261+
srcs = ["day19.cc"],
262+
hdrs = ["day19.h"],
263+
deps = [
264+
"@com_google_absl//absl/strings",
265+
],
266+
)
267+
268+
cc_test(
269+
name = "day19_test",
270+
srcs = ["day19_test.cc"],
271+
deps = [
272+
":day19",
273+
"@com_google_googletest//:gtest_main",
274+
],
275+
)
276+
277+
cc_binary(
278+
name = "day19_main",
279+
srcs = ["day19_main.cc"],
280+
deps = [
281+
":day19",
282+
],
283+
)

2018/aoc18/day19.cc

+333
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
#include "aoc18/day19.h"
2+
3+
#include <array>
4+
#include <functional>
5+
#include <iostream>
6+
#include <optional>
7+
8+
#include "absl/strings/numbers.h"
9+
#include "absl/strings/str_split.h"
10+
#include "absl/strings/string_view.h"
11+
12+
namespace aoc18 {
13+
namespace day19 {
14+
15+
using OpFn = std::function<Reg(Reg, Num, Num, Num)>;
16+
17+
static const std::array<OpFn, N_OP> OPS{
18+
/*ADDR=*/[](Reg r, Num a, Num b, Num c) -> Reg {
19+
r[c] = r[a] + r[b];
20+
return r;
21+
},
22+
/*ADDI=*/
23+
[](Reg r, Num a, Num b, Num c) -> Reg {
24+
r[c] = r[a] + b;
25+
return r;
26+
},
27+
/*MULR=*/
28+
[](Reg r, Num a, Num b, Num c) -> Reg {
29+
r[c] = r[a] * r[b];
30+
return r;
31+
},
32+
/*MULI=*/
33+
[](Reg r, Num a, Num b, Num c) -> Reg {
34+
r[c] = r[a] * b;
35+
return r;
36+
},
37+
/*BANR=*/
38+
[](Reg r, Num a, Num b, Num c) -> Reg {
39+
r[c] = r[a] & r[b];
40+
return r;
41+
},
42+
/*BANI=*/
43+
[](Reg r, Num a, Num b, Num c) -> Reg {
44+
r[c] = r[a] & b;
45+
return r;
46+
},
47+
/*BORR=*/
48+
[](Reg r, Num a, Num b, Num c) -> Reg {
49+
r[c] = r[a] | r[b];
50+
return r;
51+
},
52+
/*BORI=*/
53+
[](Reg r, Num a, Num b, Num c) -> Reg {
54+
r[c] = r[a] | b;
55+
return r;
56+
},
57+
/*SETR=*/
58+
[](Reg r, Num a, Num _, Num c) -> Reg {
59+
r[c] = r[a];
60+
return r;
61+
},
62+
/*SETI=*/
63+
[](Reg r, Num a, Num _, Num c) -> Reg {
64+
r[c] = a;
65+
return r;
66+
},
67+
/*GTIR=*/
68+
[](Reg r, Num a, Num b, Num c) -> Reg {
69+
r[c] = a > r[b];
70+
return r;
71+
},
72+
/*GTRI=*/
73+
[](Reg r, Num a, Num b, Num c) -> Reg {
74+
r[c] = r[a] > b;
75+
return r;
76+
},
77+
/*GTRR=*/
78+
[](Reg r, Num a, Num b, Num c) -> Reg {
79+
r[c] = r[a] > r[b];
80+
return r;
81+
},
82+
/*EQIR=*/
83+
[](Reg r, Num a, Num b, Num c) -> Reg {
84+
r[c] = a == r[b];
85+
return r;
86+
},
87+
/*EQRI=*/
88+
[](Reg r, Num a, Num b, Num c) -> Reg {
89+
r[c] = r[a] == b;
90+
return r;
91+
},
92+
/*EQRR=*/
93+
[](Reg r, Num a, Num b, Num c) -> Reg {
94+
r[c] = r[a] == r[b];
95+
return r;
96+
}};
97+
98+
Reg Run(const Prog& prog, Reg r) {
99+
Num& ip = r[prog.ip];
100+
while (ip >= 0 && ip < prog.ins.size()) {
101+
const Ins& i = prog.ins[ip];
102+
r = OPS[i.op](r, i.a, i.b, i.c);
103+
++ip;
104+
}
105+
return r;
106+
}
107+
108+
std::optional<Op> ParseOp(std::string_view s) {
109+
if (s == "addr") {
110+
return ADDR;
111+
} else if (s == "addi") {
112+
return ADDI;
113+
} else if (s == "mulr") {
114+
return MULR;
115+
} else if (s == "muli") {
116+
return MULI;
117+
} else if (s == "banr") {
118+
return BANR;
119+
} else if (s == "bani") {
120+
return BANI;
121+
} else if (s == "borr") {
122+
return BORR;
123+
} else if (s == "bori") {
124+
return BORI;
125+
} else if (s == "setr") {
126+
return SETR;
127+
} else if (s == "seti") {
128+
return SETI;
129+
} else if (s == "gtir") {
130+
return GTIR;
131+
} else if (s == "gtri") {
132+
return GTRI;
133+
} else if (s == "gtrr") {
134+
return GTRR;
135+
} else if (s == "eqir") {
136+
return EQIR;
137+
} else if (s == "eqri") {
138+
return EQRI;
139+
} else if (s == "eqrr") {
140+
return EQRR;
141+
}
142+
return std::nullopt;
143+
}
144+
145+
std::optional<Ins> ParseIns(absl::string_view s) {
146+
std::vector<absl::string_view> parts = absl::StrSplit(s, ' ');
147+
if (parts.size() != 4) {
148+
return std::nullopt;
149+
}
150+
auto maybe_op = ParseOp(parts[0]);
151+
if (!maybe_op.has_value()) {
152+
return std::nullopt;
153+
}
154+
Ins ins{.op = maybe_op.value()};
155+
if (!absl::SimpleAtoi(parts[1], &ins.a)) {
156+
return std::nullopt;
157+
}
158+
if (!absl::SimpleAtoi(parts[2], &ins.b)) {
159+
return std::nullopt;
160+
}
161+
if (!absl::SimpleAtoi(parts[3], &ins.c)) {
162+
return std::nullopt;
163+
}
164+
return ins;
165+
}
166+
167+
std::optional<std::size_t> ParseIp(absl::string_view s) {
168+
// Remove "#ip "
169+
s.remove_prefix(4);
170+
std::size_t ip;
171+
if (!absl::SimpleAtoi(s, &ip)) {
172+
return std::nullopt;
173+
}
174+
return ip;
175+
}
176+
177+
std::optional<Prog> ParseProg(const std::string& input) {
178+
std::vector<absl::string_view> lines =
179+
absl::StrSplit(input, '\n', absl::SkipEmpty());
180+
auto maybe_ip = ParseIp(lines[0]);
181+
if (!maybe_ip.has_value()) {
182+
return std::nullopt;
183+
}
184+
Prog prog{.ip = maybe_ip.value()};
185+
for (auto it = lines.begin() + 1; it != lines.end(); ++it) {
186+
auto maybe_ins = ParseIns(*it);
187+
if (!maybe_ins.has_value()) {
188+
return std::nullopt;
189+
}
190+
prog.ins.push_back(maybe_ins.value());
191+
}
192+
return prog;
193+
}
194+
195+
int Part1(const Prog& prog) {
196+
auto res = Run(prog, Reg{0, 0, 0, 0, 0, 0});
197+
return res[0];
198+
}
199+
200+
int Part2(const Prog& prog) {
201+
// Part two is a somewhat longer adventure. First, I attempted to run the
202+
// code as in part 1, just with Reg{1, 0, ...}. Unfortunately, this doesn't
203+
// really seem to terminate in an acceptable time.
204+
// So I looked at the assembly…
205+
206+
// First, I translated the assembly code to an assignment syntax, that made
207+
// it easier for me to understand what was going on.
208+
//
209+
// 0: ip = ip + 16 -- goto 17
210+
// 1: r5 = 1
211+
// 2: r4 = 1
212+
// 3: r2 = r5 * r4
213+
// 4: r2 = r2 == r1 -- if (r2 == r1) {
214+
// 5: ip = r2 + ip -- goto 7 } else {
215+
// 6: ip = ip + 1 -- goto 8 }
216+
// 7: r0 = r5 + r0
217+
// 8: r4 = r4 + 1
218+
// 9: r2 = r4 > r1 -- if (r4 > r1) {
219+
// 10: ip = ip + r2 -- goto 12 } else {
220+
// 11: ip = 2 -- goto 3 }
221+
// 12: r5 = r5 + 1
222+
// 13: r2 = r5 > r1 -- if (r5 > r1) {
223+
// 14: ip = r2 + ip -- goto 16 } else {
224+
// 15: ip = 1 -- goto 2 }
225+
// 16: ip = ip * ip -- goto end (16 * 16 = 255)
226+
// 17: r1 = r1 + 2
227+
// 18: r1 = r1 * r1
228+
// 19: r1 = ip * r1
229+
// 20: r1 = r1 * 11
230+
// 21: r2 = r2 + 2
231+
// 22: r2 = r2 * ip
232+
// 23: r2 = r2 + 20
233+
// 24: r1 = r1 + r2
234+
// 25: ip = ip + r0 -- goto r0 + 25 + 1
235+
// 26: ip = 0 -- (if r0 == 0) goto 1
236+
// 27: r2 = ip
237+
// 28: r2 = r2 * ip
238+
// 29: r2 = ip + r2
239+
// 30: r2 = ip * r2
240+
// 31: r2 = r2 * 14
241+
// 32: r2 = r2 * ip
242+
// 33: r1 = r1 + r2
243+
// 34: r0 = 0
244+
// 35: ip = 0 -- goto 1
245+
246+
// Then, translated this to C code and gradually simplified it. At the end, I
247+
// was left with the following program.
248+
// #include <stdio.h>
249+
//
250+
// int main() {
251+
// int r0 = 0;
252+
// int r1 = 0;
253+
// int r2 = 0;
254+
// int r4 = 0;
255+
// int r5 = 0;
256+
//
257+
// // This block starts on line 17 and is only executed once. It
258+
// // initializes r1 and r2 depending on whether r0 is set to 0 (part 1)
259+
// // or 1 (part 2).
260+
//
261+
// r1 += 2;
262+
// r1 = (r1 * r1) * 19 * 11;
263+
// r2 = (r2 + 2) * 22 + 20;
264+
// r1 += r2;
265+
// if (r0) {
266+
// r2 = (27 * 28 + 29) * 30 * 14 * 32;
267+
// r1 += r2;
268+
// r0 = 0;
269+
// }
270+
// // At this point, r1 contains a large number that will be relevant
271+
// // further down. It's 900 for part 1 and 10551300 for part 2.
272+
//
273+
// // This is the actual program logic starting from line 1. Down here,
274+
// // r2 is only used to evaluate booleans so we can simplify all
275+
// // expressions that contain it. The resulting code with gotos looks
276+
// // like this
277+
// // r5 = 1;
278+
// // line2: // line 2
279+
// // r4 = 1;
280+
// // line3: // line 3
281+
// // if ((r4 * r5) == r1) {
282+
// // r0 += r5;
283+
// // }
284+
// // r4 += 1;
285+
// // if (r4 <= r1) {
286+
// // goto line3;
287+
// // }
288+
// // r5 += 1;
289+
// // if (r5 <= r1) {
290+
// // goto line2;
291+
// // }
292+
// // which can be further simplified into two nested for loops.
293+
//
294+
// for (r5 = 1; r5 <= r1; r5++) {
295+
// for (r4 = 1; r4 <= r1; r4++) {
296+
// if (r4 * r5 == r1) {
297+
// r0 += r5;
298+
// }
299+
// }
300+
// }
301+
// printf("%d\n", r0);
302+
// }
303+
//
304+
// So what this code actually does is it calculates the sum of all the
305+
// divisors of our number. In order to get the stars, we can do that faster
306+
// here. So we run the program for a few steps to guarantee our r1 has been
307+
// calculated (afterwards it doesn't change anymore for the rest of the
308+
// program).
309+
310+
auto RunSteps = [](const Prog& prog, Reg r, int n) -> Reg {
311+
Num& ip = r[prog.ip];
312+
for (int s{0}; s < n; ++s) {
313+
const Ins& i = prog.ins[ip];
314+
r = OPS[i.op](r, i.a, i.b, i.c);
315+
++ip;
316+
}
317+
return r;
318+
};
319+
Reg r{1, 0, 0, 0, 0, 0};
320+
Reg res = RunSteps(prog, r, 10000);
321+
322+
int num = res[1];
323+
int sum{num};
324+
for (int i{1}; i <= num / 2; ++i) {
325+
if (num % i == 0) {
326+
sum += i;
327+
}
328+
}
329+
return sum;
330+
}
331+
332+
} // namespace day19
333+
} // namespace aoc18

0 commit comments

Comments
 (0)