Skip to content

Commit ebae413

Browse files
committed
Year 2018 Day 19
1 parent 786108b commit ebae413

File tree

7 files changed

+136
-0
lines changed

7 files changed

+136
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
255255
| 16 | [Chronal Classification](https://adventofcode.com/2018/day/16) | [Source](src/year2018/day16.rs) | 36 |
256256
| 17 | [Reservoir Research ](https://adventofcode.com/2018/day/17) | [Source](src/year2018/day17.rs) | 145 |
257257
| 18 | [Settlers of The North Pole](https://adventofcode.com/2018/day/18) | [Source](src/year2018/day18.rs) | 387 |
258+
| 19 | [Go With The Flow](https://adventofcode.com/2018/day/19) | [Source](src/year2018/day19.rs) | 1 |
258259

259260
## 2017
260261

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ mod year2018 {
142142
benchmark!(year2018, day16);
143143
benchmark!(year2018, day17);
144144
benchmark!(year2018, day18);
145+
benchmark!(year2018, day19);
145146
}
146147

147148
mod year2019 {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub mod year2018 {
130130
pub mod day16;
131131
pub mod day17;
132132
pub mod day18;
133+
pub mod day19;
133134
}
134135

135136
/// # Rescue Santa from deep space with a solar system voyage.

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ fn year2018() -> Vec<Solution> {
198198
solution!(year2018, day16),
199199
solution!(year2018, day17),
200200
solution!(year2018, day18),
201+
solution!(year2018, day19),
201202
]
202203
}
203204

src/year2018/day19.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! # Go With The Flow
2+
//!
3+
//! There are two parts to this problem:
4+
//! * Reverse engineering the assembly in order to figure out what the program is doing.
5+
//! * Implementing the program more efficiently in Rust.
6+
//!
7+
//! ## Reverse Engineering
8+
//!
9+
//! ```none
10+
//! Raw | Pseudo-Assembly | Pseudo-Rust
11+
//! -----------------+----------------------------------+-----------------------------------
12+
//! #ip 1 | # a = 0 b = 2 c = 3 d = 4 e = 5 |
13+
//! addi 1 16 1 | goto hotel |
14+
//! seti 1 8 2 | alfa: b = 1 | for b in 1..=e {
15+
//! seti 1 5 4 | bravo: d = 1 | for d in 1..=e {
16+
//! mulr 2 4 3 | charlie: c = b * d |
17+
//! eqrr 3 5 3 | c = (c == e) ? 1: 0 |
18+
//! addr 3 1 1 | if c == 1 goto delta | if b * d == e {
19+
//! addi 1 1 1 | goto echo |
20+
//! addr 2 0 0 | delta: a += b | a += b
21+
//! addi 4 1 4 | echo: d += 1 |
22+
//! gtrr 4 5 3 | c = (d > e) ? 1: 0 | }
23+
//! addr 1 3 1 | if c == 1 goto foxtrot |
24+
//! seti 2 8 1 | goto charlie | }
25+
//! addi 2 1 2 | foxtrot: b += 1 |
26+
//! gtrr 2 5 3 | c = (b > e) ? 1: 0 |
27+
//! addr 3 1 1 | if c == 1 goto golf |
28+
//! seti 1 8 1 | goto bravo | }
29+
//! mulr 1 1 1 | golf: goto end |
30+
//! addi 5 2 5 | hotel: e = 2 |
31+
//! mulr 5 5 5 | e = e * e |
32+
//! mulr 1 5 5 | e *= 19 |
33+
//! muli 5 11 5 | e *= 11 |
34+
//! addi 3 $FIRST 3 | c = $FIRST |
35+
//! mulr 3 1 3 | c *= 22 |
36+
//! addi 3 $SECOND 3 | c += $SECOND |
37+
//! addr 5 3 5 | e += c | e = (22 * $FIRST + $SECOND) + 836
38+
//! addr 1 0 1 | if a == 1 goto india |
39+
//! seti 0 7 1 | goto alfa |
40+
//! setr 1 1 3 | india: c = 27 |
41+
//! mulr 3 1 3 | c *= 28 |
42+
//! addr 1 3 3 | c += 29 |
43+
//! mulr 1 3 3 | c *= 30 |
44+
//! muli 3 14 3 | c *= 14 |
45+
//! mulr 3 1 3 | c *= 32 |
46+
//! addr 5 3 5 | e += c | if a == 1 { e += 10550400 }
47+
//! seti 0 9 0 | a = 0 |
48+
//! seti 0 0 1 | goto alfa |
49+
//! | end: |
50+
//! ```
51+
//!
52+
//! The decoded assembly shows that the program is computing the
53+
//! [sum of the divisors](https://en.wikipedia.org/wiki/Divisor_summatory_function) of a number `n`,
54+
//! using two nested loops for a total complexity in part two of `O(n²) = O(10¹⁴)`.
55+
//!
56+
//! Clearly there is some room for performance improvements. The interesting part is that we only
57+
//! need the two numbers `$FIRST` and `$SECOND` and can discard the rest of the input.
58+
//!
59+
//! ## Rust Implementation
60+
//!
61+
//! We compute the divisor sum using [trial division](https://en.wikipedia.org/wiki/Trial_division).
62+
//! As we want the prime factors (instead of checking that `n` is prime) the asymptotic complexity
63+
//! is slightly lower in practice, being the square root of the largest prime factor of `n`
64+
//! instead of the square root of `n` itself.
65+
//!
66+
//! As `n` is on the order of 10,000,000 this gives a worst case upper bound of `√10000000 = 3162`
67+
//! when `n` is prime. However for most composite numbers the largest prime factor will be much
68+
//! smaller, on the order of 100,000 for an approximate complexity of `√100000 = 316`.
69+
use crate::util::parse::*;
70+
71+
type Input = (u32, u32);
72+
73+
/// Extracts the two unique numbers from the input then calculates the composite numbers
74+
/// needed for both parts.
75+
pub fn parse(input: &str) -> Input {
76+
let tokens: Vec<u32> = input.iter_unsigned().collect();
77+
let base = 22 * tokens[65] + tokens[71];
78+
(base + 836, base + 10551236)
79+
}
80+
81+
pub fn part1(input: &Input) -> u32 {
82+
divisor_sum(input.0)
83+
}
84+
85+
pub fn part2(input: &Input) -> u32 {
86+
divisor_sum(input.1)
87+
}
88+
89+
/// Returns the sum of the divisors of an integer `n`, including 1 and `n` itself.
90+
/// For example `20 => 1 + 2 + 4 + 5 + 10 + 20 = 42`.
91+
fn divisor_sum(mut n: u32) -> u32 {
92+
let mut f = 2;
93+
let mut sum = 1;
94+
95+
// We only need to check factors less than or equal to the square root of the greatest prime
96+
// factor of the input. This loop will only consider prime numbers since we will have sieved
97+
// out smaller primes. For example `n = 20 = 2 * 2 * 5`. When we check `f = 4`, `n` will
98+
// already be reduced to 5.
99+
while f * f <= n {
100+
// `g` is the next term in the geometric series
101+
// representing the sum of a repeated prime factor.
102+
let mut g = sum;
103+
104+
// `n` could have more than one of the same prime factor.
105+
while n % f == 0 {
106+
n /= f;
107+
g *= f;
108+
sum += g;
109+
}
110+
111+
f += 1;
112+
}
113+
114+
// If `n` is one then the greatest prime factor was repeated so has already been included in
115+
// the sum and we can just return it directly. Otherwise `n` is the unique greatest prime
116+
// factor and must be added to the sum.
117+
if n == 1 {
118+
sum
119+
} else {
120+
sum * (1 + n)
121+
}
122+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ mod year2018 {
131131
mod day16_test;
132132
mod day17_test;
133133
mod day18_test;
134+
mod day19_test;
134135
}
135136

136137
mod year2019 {

tests/year2018/day19_test.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[test]
2+
fn part1_test() {
3+
// No example data
4+
}
5+
6+
#[test]
7+
fn part2_test() {
8+
// No example data
9+
}

0 commit comments

Comments
 (0)