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}