summaryrefslogtreecommitdiff
path: root/d12/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'd12/src/main.rs')
-rw-r--r--d12/src/main.rs129
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);
+ }
+}