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