use itertools::Itertools; use std::convert::TryFrom; use std::io::{self, BufRead}; use std::ops::Range; #[derive(Debug)] enum Operation { Add { x: i64, y: i64, addr: usize }, Multiply { x: i64, y: i64, addr: usize }, Input { value: i64, addr: usize }, Output { value: i64 }, JumpIfTrue { value: i64, addr: i64 }, JumpIfFalse { value: i64, addr: i64 }, LessThan { first: i64, second: i64, addr: i64 }, Equals { first: i64, second: i64, addr: i64 }, } #[derive(Debug)] enum Mode { Immediate, Position, } enum ParameterPosition { First, Second, } struct Machine { pos: i64, tape: Vec, params: Vec, } impl Into for ParameterPosition { fn into(self) -> usize { match self { ParameterPosition::First => 2, ParameterPosition::Second => 3, } } } impl Into for &char { fn into(self) -> Mode { match self { '0' => Mode::Position, '1' => Mode::Immediate, _ => unreachable!(), } } } fn get_next(input: &[i64], pos: &mut i64, mode: Mode) -> i64 { let value = input[*pos as usize]; let next = match mode { Mode::Position => input[value as usize], Mode::Immediate => value, }; *pos += 1; next } fn get_mode(raw_opcode: &[char], pos: ParameterPosition) -> Mode { raw_opcode.get::(pos.into()).unwrap_or(&'0').into() } #[rustfmt::skip] fn decode_next(machine: &mut Machine) -> Option { let next = get_next(&machine.tape, &mut machine.pos, Mode::Immediate); let mut raw_opcode: Vec<_> = next.to_string().chars().collect(); raw_opcode.reverse(); match raw_opcode[0] { '1' => Some(Operation::Add { x: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), y: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), addr: get_next(&machine.tape, &mut machine.pos, Mode::Immediate) as usize, }), '2' => Some(Operation::Multiply { x: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), y: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), addr: get_next(&machine.tape, &mut machine.pos, Mode::Immediate) as usize, }), '3' => Some(Operation::Input { value: machine.params.pop().unwrap(), addr: get_next(&machine.tape, &mut machine.pos, Mode::Immediate) as usize, }), '4' => Some(Operation::Output { value: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), }), '5' => Some(Operation::JumpIfTrue { value: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), addr: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), }), '6' => Some(Operation::JumpIfFalse { value: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), addr: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), }), '7' => Some(Operation::LessThan { first: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), second: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), addr: get_next(&machine.tape, &mut machine.pos, Mode::Immediate), }), '8' => Some(Operation::Equals { first: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::First)), second: get_next(&machine.tape, &mut machine.pos, get_mode(&raw_opcode, ParameterPosition::Second)), addr: get_next(&machine.tape, &mut machine.pos, Mode::Immediate), }), '9' => None, _ => unreachable!(), } } fn execute(op: Operation, input: &mut Vec, pos: &mut i64) -> Option { match op { Operation::Add { x, y, addr } => { input[addr as usize] = x + y; None } Operation::Multiply { x, y, addr } => { input[addr as usize] = x * y; None } Operation::Input { value, addr } => { input[addr] = value; None } Operation::Output { value } => Some(value), Operation::JumpIfTrue { value, addr } => { if value != 0 { *pos = addr } None } Operation::JumpIfFalse { value, addr } => { if value == 0 { *pos = addr } None } Operation::LessThan { first, second, addr, } => { input[addr as usize] = (first < second) as i64; None } Operation::Equals { first, second, addr, } => { input[addr as usize] = (first == second) as i64; None } } } pub fn run_for_input(input: &Vec, acc: &mut i64, amps: Vec) -> i64 { let mut machines: Vec<_> = amps .into_iter() .map(|amp| Machine { tape: input.clone(), pos: 0, params: vec![amp], }) .collect(); for state in (0..machines.len()).cycle() { let mut machine = machines.get_mut(state).unwrap(); machine.params.insert(0, *acc); match execute_machine(&mut machine) { Err(output) => *acc = output, Ok(_) => break, } } *acc } pub fn find_max(range: Range, input: &Vec) -> Option { usize::try_from(range.end - range.start) .ok() .and_then(|len| { range .permutations(len) .scan(0, |&mut mut acc, amps| { Some(run_for_input(input, &mut acc, amps)) }) .max() }) } fn execute_machine(machine: &mut Machine) -> Result { loop { match decode_next(machine) { Some(op) => { if let Some(o) = execute(op, &mut machine.tape, &mut machine.pos) { return Err(o); } } None => return Ok(machine.params.pop().unwrap()), } } } #[rustfmt::skip] pub fn read_input() -> Vec { io::stdin().lock().lines().next().unwrap().unwrap().split(',').map(|n| n.parse().unwrap()).collect() } #[cfg(test)] mod tests;