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
//! # Chronal Conversion
//!
//! Just like [`Day 19`] we reverse engineer the assembly to figure out what the program is doing,
//! then implement the program more efficiently in Rust.
//!
//! ```none
//! Raw | Pseudo-Assembly | Pseudo-Rust
//! ------------------+---------------------------------+---------------------------------------
//! #ip 2 | # a = 0 b = 1 c = 3 d = 4 e = 5 |
//! seti 123 0 3 | c = 123 | // Test start
//! bani 3 456 3 | alfa: c &= 456 | //
//! eqri 3 72 3 | c = (c == 72) ? 1: 0 | // Irrelevant to rest of program.
//! addr 3 2 2 | if c == 1 goto bravo | //
//! seti 0 0 2 | goto alfa | // Test end
//! seti 0 4 3 | bravo: c = 0 | do {
//! bori 3 65536 4 | charlie: d = c | 65536 | d = c | 0x10000;
//! seti $SEED 3 3 | c = $SEED | c = $SEED
//! bani 4 255 5 | delta: e = d & 255 | while d > 0 {
//! addr 3 5 3 | c += e |
//! bani 3 16777215 3 | c &= 16777215 | c = (c + (d & 0xff)) & 0xffffff;
//! muli 3 65899 3 | c *= 65899 |
//! bani 3 16777215 3 | c &= 16777215 | c = (c * 65899) & 0xffffff;
//! gtir 256 4 5 | e = (256 > d) ? 1: 0 | // Break condition for
//! addr 5 2 2 | if e == 1 goto echo | // loop. Loop will always
//! addi 2 1 2 | goto foxtrot | // execute 3 times.
//! seti 27 0 2 | echo: goto kilo |
//! seti 0 2 5 | foxtrot: e = 0 | // Start inner loop
//! addi 5 1 1 | golf: b = e + 1 | //
//! muli 1 256 1 | b *= 256 | //
//! gtrr 1 4 1 | b = (b > d) 1: 0 | //
//! addr 1 2 2 | if b == 1 goto hotel | // This loop computes d
//! addi 2 1 2 | goto india | // shifted right by 8 bits.
//! seti 25 3 2 | hotel: goto juliet | //
//! addi 5 1 5 | india: e += 1 | //
//! seti 17 3 2 | goto golf | // End inner loop
//! setr 5 3 4 | juliet: d = e | d >>= 8;
//! seti 7 4 2 | goto delta | }
//! eqrr 3 0 5 | kilo: e = (c == a) ? 1: 0 | } while (c != a);
//! addr 5 2 2 | if e == 1 goto end |
//! seti 5 8 2 | goto charlie |
//! | end: |
//! ```
//!
//! Starting with `0` the program computes a series of hashes, terminating once the hash
//! is equal to register 0. `$SEED` is the only value that we need from the input.
//!
//! For part one, in order to execute the fewest instructions, the loop should terminate after
//! one repetition so register 0 should contain the value of the first hash.
//!
//! Part two is more subtle. Analyzing the hash values shows that they eventually form a
//! [cycle](https://en.wikipedia.org/wiki/Cycle_detection). To execute the most instructions but
//! still terminate, register 0 should be equal to the *last* value of the cycle (assuming that
//! the seed value is chosen so that this hash does not appear outside the cycle). For example:
//!
//! ```none
//! 0 => 7 => 1 => [4 = > 5 => 3 => 2] => [4 => ...]
//! ```
//!
//! The cycle starts with `4` and ends with `2`, so the answer is `2`.
//!
//! [`Day 19`]: crate::year2018::day19
use crate::util::hash::*;
use crate::util::parse::*;
pub fn parse(input: &str) -> u64 {
input.iter_unsigned().nth(22).unwrap()
}
/// Execute the loop just once.
pub fn part1(input: &u64) -> u64 {
step(*input, 0)
}
/// Find the last value in the cycle of output hashes.
pub fn part2(input: &u64) -> u64 {
let mut prev = 0;
let mut hash = 0;
let mut seen = FastSet::with_capacity(20_000);
while seen.insert(hash) {
prev = hash;
hash = step(*input, hash);
}
prev
}
/// Implements the program hash function.
fn step(seed: u64, hash: u64) -> u64 {
let mut c = seed;
let mut d = hash | 0x10000;
for _ in 0..3 {
c = (c + (d & 0xff)) & 0xffffff;
c = (c * 65899) & 0xffffff;
d >>= 8;
}
c
}