diff --git a/README.md b/README.md index 8aacc39..f869159 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 9](./src/bin/09.rs) | `77.2µs` | `969.4µs` | | [Day 10](./src/bin/10.rs) | `164.5µs` | `287.9µs` | | [Day 11](./src/bin/11.rs) | `30.5s` | `-` | +| [Day 12](./src/bin/12.rs) | `3.1ms` | `91.5ms` | -**Total: 47833.76ms** +**Total: 47928.36ms** --- diff --git a/data/examples/12.txt b/data/examples/12.txt new file mode 100644 index 0000000..01a004b --- /dev/null +++ b/data/examples/12.txt @@ -0,0 +1,6 @@ +cpy 41 a +inc a +inc a +dec a +jnz a 2 +dec a diff --git a/src/bin/12.rs b/src/bin/12.rs new file mode 100644 index 0000000..5a823bf --- /dev/null +++ b/src/bin/12.rs @@ -0,0 +1,198 @@ +advent_of_code::solution!(12); + +// Could do with just a `char` but less place for confusion that way +#[derive(Debug, Clone, Copy)] +enum Register { + A, + B, + C, + D, +} + +// Well... clearly it would be easier to just use usize... Buuuut. Less confusing... +impl Register { + fn idx(self) -> usize { + match self { + Register::A => 0, + Register::B => 1, + Register::C => 2, + Register::D => 3, + } + } +} + +impl TryFrom<&&str> for Register { + type Error = (); + fn try_from(value: &&str) -> Result { + match *value { + "a" => Ok(Register::A), + "b" => Ok(Register::B), + "c" => Ok(Register::C), + "d" => Ok(Register::D), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy)] +enum Value { + Reg(Register), + Const(i8), +} + +impl TryFrom<&&str> for Value { + type Error = (); + fn try_from(value: &&str) -> Result { + Register::try_from(value) + .map(Value::Reg) + .or_else(|_| value.parse::().map(Value::Const).map_err(|_| ())) + } +} + +impl std::fmt::Debug for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Value::Reg(r) => write!(f, "[{:?}]", r)?, + Value::Const(c) => write!(f, "{:>3?}", c)?, + } + Ok(()) + } +} + +#[derive(Clone)] +enum Instr { + Copy(Value, Register), + Increment(Register), + Decrement(Register), + JumpNotZero(Value, Value), +} + +impl TryFrom<&str> for Instr { + type Error = (); + fn try_from(line: &str) -> Result { + let token: Vec<&str> = line.split_whitespace().collect(); + match token.as_slice() { + ["cpy", src, dst] => Ok(Instr::Copy(src.try_into()?, dst.try_into()?)), + ["inc", reg] => Ok(Instr::Increment(reg.try_into()?)), + ["dec", reg] => Ok(Instr::Decrement(reg.try_into()?)), + ["jnz", val, offset] => Ok(Instr::JumpNotZero(val.try_into()?, offset.try_into()?)), + _ => Err(()), + } + } +} + +impl std::fmt::Debug for Instr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Instr::Copy(val, reg) => write!(f, "CPY {:>3?} -> [{:?}]", val, reg)?, + Instr::Increment(reg) => write!(f, "INC [{:?}]", reg)?, + Instr::Decrement(reg) => write!(f, "DEC [{:?}]", reg)?, + Instr::JumpNotZero(Value::Const(0), offset) => { + write!(f, "JNZ 0 off: {:?} -> NOOP", offset)? + } + Instr::JumpNotZero(cond, offset) => write!(f, "JNZ {:3?} off: {:?}", cond, offset)?, + } + Ok(()) + } +} + +struct State { + rs: [i64; 4], + ins: Vec, + ins_ptr: i8, +} + +impl State { + fn init(ins: Vec) -> Self { + Self { + rs: [0; 4], + ins, + ins_ptr: 0, + } + } + + fn step(&mut self) -> bool { + if self.ins_ptr as usize >= self.ins.len() || self.ins_ptr < 0 { + return false; + } + match &self.ins[self.ins_ptr as usize] { + Instr::Copy(Value::Const(c), dst) => self.rs[dst.idx()] = *c as i64, + Instr::Copy(Value::Reg(src), dst) => self.rs[dst.idx()] = self.rs[src.idx()], + Instr::Increment(reg) => self.rs[reg.idx()] += 1, + Instr::Decrement(reg) => self.rs[reg.idx()] -= 1, + Instr::JumpNotZero(Value::Const(0), _) => (), + Instr::JumpNotZero(Value::Const(_), Value::Const(c)) => self.ins_ptr += c - 1, + Instr::JumpNotZero(Value::Const(_), Value::Reg(reg)) => { + self.ins_ptr += (self.rs[reg.idx()] - 1) as i8 + } + Instr::JumpNotZero(Value::Reg(r), offset) => { + if self.rs[r.idx()] != 0 { + self.ins_ptr += match offset { + Value::Const(c) => c - 1, + Value::Reg(r_off) => (self.rs[r_off.idx()] - 1) as i8, + } + } + } + } + self.ins_ptr += 1; + true + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + writeln!(f, "Reg: [ A ] [ B ] [ C ] [ D ]")?; + write!(f, " ")?; + for r in self.rs { + write!(f, " [{:^3?}]", r)?; + } + writeln!(f, "\nProgram:")?; + for (n, ins) in self.ins.clone().into_iter().enumerate() { + if n as i8 == self.ins_ptr { + write!(f, "-> ")?; + } else { + write!(f, " ")?; + } + writeln!(f, "{:>3}: {:?}", n, ins)?; + } + Ok(()) + } +} + +pub fn part_one(input: &str) -> Option { + let instructions: Vec = input.lines().flat_map(Instr::try_from).collect(); + let mut state = State::init(instructions); + // println!("{:?}", state); + while state.step() { + // println!("{:?}", state); + } + Some(state.rs[0]) +} + +pub fn part_two(input: &str) -> Option { + let instructions: Vec = input.lines().flat_map(Instr::try_from).collect(); + let mut state = State::init(instructions); + state.rs[Register::C.idx()] = 1; + // println!("{:?}", state); + while state.step() { + // println!("{:?}", state); + } + Some(state.rs[0]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(42)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(42)); + } +}