aoc/year2018/
day21.rs

1//! # Chronal Conversion
2//!
3//! Just like [`Day 19`] we reverse engineer the assembly to figure out what the program is doing,
4//! then implement the program more efficiently in Rust.
5//!
6//! ```none
7//!     Raw               | Pseudo-Assembly                 | Pseudo-Rust
8//!     ------------------+---------------------------------+---------------------------------------
9//!     #ip 2             | # a = 0 b = 1 c = 3 d = 4 e = 5 |
10//!     seti 123 0 3      |          c = 123                | // Test start
11//!     bani 3 456 3      | alfa:    c &= 456               | //
12//!     eqri 3 72 3       |          c = (c == 72) ? 1: 0   | // Irrelevant to rest of program.
13//!     addr 3 2 2        |          if c == 1 goto bravo   | //
14//!     seti 0 0 2        |          goto alfa              | // Test end
15//!     seti 0 4 3        | bravo:   c = 0                  | do {
16//!     bori 3 65536 4    | charlie: d = c | 65536          |     d = c | 0x10000;
17//!     seti $SEED 3 3    |          c = $SEED              |     c = $SEED
18//!     bani 4 255 5      | delta:   e = d & 255            |     while d > 0 {
19//!     addr 3 5 3        |          c += e                 |
20//!     bani 3 16777215 3 |          c &= 16777215          |         c = (c + (d & 0xff)) & 0xffffff;
21//!     muli 3 65899 3    |          c *= 65899             |
22//!     bani 3 16777215 3 |          c &= 16777215          |         c = (c * 65899) & 0xffffff;
23//!     gtir 256 4 5      |          e = (256 > d) ? 1: 0   |         // Break condition for
24//!     addr 5 2 2        |          if e == 1 goto echo    |         // loop. Loop will always
25//!     addi 2 1 2        |          goto foxtrot           |         // execute 3 times.
26//!     seti 27 0 2       | echo:    goto kilo              |
27//!     seti 0 2 5        | foxtrot: e = 0                  |         // Start inner loop
28//!     addi 5 1 1        | golf:    b = e + 1              |         //
29//!     muli 1 256 1      |          b *= 256               |         //
30//!     gtrr 1 4 1        |          b = (b > d) 1: 0       |         //
31//!     addr 1 2 2        |          if b == 1 goto hotel   |         // This loop computes d
32//!     addi 2 1 2        |          goto india             |         // shifted right by 8 bits.
33//!     seti 25 3 2       | hotel:   goto juliet            |         //
34//!     addi 5 1 5        | india:   e += 1                 |         //
35//!     seti 17 3 2       |          goto golf              |         // End inner loop
36//!     setr 5 3 4        | juliet:  d = e                  |         d >>= 8;
37//!     seti 7 4 2        |          goto delta             |     }
38//!     eqrr 3 0 5        | kilo:    e = (c == a) ? 1: 0    | } while (c != a);
39//!     addr 5 2 2        |          if e == 1 goto end     |
40//!     seti 5 8 2        |          goto charlie           |
41//!                       | end:                            |
42//! ```
43//!
44//! Starting with `0` the program computes a series of hashes, terminating once the hash
45//! is equal to register 0. `$SEED` is the only value that we need from the input.
46//!
47//! For part one, in order to execute the fewest instructions, the loop should terminate after
48//! one repetition so register 0 should contain the value of the first hash.
49//!
50//! Part two is more subtle. Analyzing the hash values shows that they eventually form a
51//! [cycle](https://en.wikipedia.org/wiki/Cycle_detection). To execute the most instructions but
52//! still terminate, register 0 should be equal to the *last* value of the cycle (assuming that
53//! the seed value is chosen so that this hash does not appear outside the cycle). For example:
54//!
55//! ```none
56//!     0 => 7 => 1 =>  [4 = > 5 => 3 => 2] => [4 => ...]
57//! ```
58//!
59//! The cycle starts with `4` and ends with `2`, so the answer is `2`.
60//!
61//! [`Day 19`]: crate::year2018::day19
62use crate::util::hash::*;
63use crate::util::parse::*;
64
65pub fn parse(input: &str) -> u64 {
66    input.iter_unsigned().nth(22).unwrap()
67}
68
69/// Execute the loop just once.
70pub fn part1(input: &u64) -> u64 {
71    step(*input, 0)
72}
73
74/// Find the last value in the cycle of output hashes.
75pub fn part2(input: &u64) -> u64 {
76    let mut prev = 0;
77    let mut hash = 0;
78    let mut seen = FastSet::with_capacity(20_000);
79
80    while seen.insert(hash) {
81        prev = hash;
82        hash = step(*input, hash);
83    }
84
85    prev
86}
87
88/// Implements the program hash function.
89fn step(seed: u64, hash: u64) -> u64 {
90    let mut c = seed;
91    let mut d = hash | 0x10000;
92
93    for _ in 0..3 {
94        c = (c + (d & 0xff)) & 0xffffff;
95        c = (c * 65899) & 0xffffff;
96        d >>= 8;
97    }
98
99    c
100}