|
| 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 | +} |
0 commit comments