diff options
author | Prefetch | 2023-02-25 11:41:27 +0100 |
---|---|---|
committer | Prefetch | 2023-02-25 11:41:27 +0100 |
commit | 3b877bf4cc667eb8bcc787d145203600a4dba2d2 (patch) | |
tree | c1d247def29fcb58ae28e4ae4e4d73d1b4e1b27f /d12/src |
Initial commit
Diffstat (limited to 'd12/src')
-rw-r--r-- | d12/src/main.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/d12/src/main.rs b/d12/src/main.rs new file mode 100644 index 0000000..d49ec45 --- /dev/null +++ b/d12/src/main.rs @@ -0,0 +1,129 @@ +use std::fs; +use std::collections::HashMap; + +enum Oper<'a> { + IncReg, + DecReg, + CpyReg(&'a str), + CpyLit(isize), + JnzReg(&'a str, isize), + JnzLit(isize, isize), +} + +fn parse(lines: Vec<&str>) -> Vec<(Oper, &str)> { + // We emit tuples of type `(Oper, &str)', where the second field + // is the destination register. Storing the destination this way, + // instead of inside the `Oper' enum, helps keep `execute()' DRY. + let mut insts = Vec::new(); + + for line in lines { + let words: Vec<&str> = line.split_ascii_whitespace().collect(); + + if words[0] == "inc" { + // words[1] is register name + insts.push((Oper::IncReg, words[1])); + } else if words[0] == "dec" { + // words[1] is register name + insts.push((Oper::DecReg, words[1])); + } else if words[0] == "cpy" { + // words[1] is source, can be literal number or register name + // words[2] is destination register name + if words[1].chars().all(|c| c.is_ascii_digit() || c == '-') { // yes I know + let val = words[1].parse().unwrap(); + insts.push((Oper::CpyLit(val), words[2])); + } else { + insts.push((Oper::CpyReg(words[1]), words[2])); + } + } else if words[0] == "jnz" { + // words[1] is value, can be literal number or register name + // words[2] is offset to jump by if branch is taken + let off = words[2].parse().unwrap(); + if words[1].chars().all(|c| c.is_ascii_digit() || c == '-') { // I know + let val = words[1].parse().unwrap(); + insts.push((Oper::JnzLit(val, off), "_")); + } else { + insts.push((Oper::JnzReg(words[1], off), "_")); + } + } + } + + insts +} + +fn execute(insts: &Vec<(Oper, &str)>, init: Vec<(&str, isize)>) -> isize { + // `init' contains initial register values + let mut state: HashMap<&str, isize> = init.into_iter().collect(); + + let mut ip = 0; // instruction pointer + while ip < insts.len() { + // Get destination register now, because we'll mess with `ip' later + let dst = insts[ip].1; + + // Too early for mutable borrow of `state', so write to `tmp' instead + let mut tmp = state.get(dst).copied().unwrap_or(0); + match insts[ip].0 { + Oper::IncReg => { + tmp += 1; + }, + Oper::DecReg => { + tmp -= 1; + }, + Oper::CpyLit(val) => { + tmp = val; + }, + Oper::CpyReg(src) => { + let val = state.get(&src).copied().unwrap_or(0); + tmp = val; + }, + Oper::JnzLit(val, off) => { + if val != 0 { + // Subtract 1 to compensate for `ip += 1;' below + ip = ((ip as isize) + off - 1) as usize; + } + }, + Oper::JnzReg(src, off) => { + let val = state.get(&src).copied().unwrap_or(0); + if val != 0 { + // Subtract 1 to compensate for `ip += 1;' below + ip = ((ip as isize) + off - 1) as usize; + } + }, + }; + + // "_" is a dummy destination register used by the "jnz" instructions + if dst != "_" { + *state.entry(dst).or_insert(0) = tmp; + } + + ip += 1; + } + + state.get("a").copied().unwrap() +} + +fn main() { + // Read assembly from input text file and parse it + let input = fs::read_to_string("input.txt").unwrap(); + let lines = input.lines().collect(); + let insts = parse(lines); + + // Part 1 gives 318117 for my input + println!("Part 1 solution: {}", execute(&insts, vec![])); + + // Part 2 gives 9227771 for my input + println!("Part 2 solution: {}", execute(&insts, vec![("c", 1)])); +} + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example1() { + let lines = vec!["cpy 41 a", "inc a", "inc a", "dec a", "jnz a 2", "dec a"]; + let insts = parse(lines); + assert_eq!(execute(&insts, vec![]), 42); + } +} |