From 55d00bbaa321b047149022286939b2e2b3dfe912 Mon Sep 17 00:00:00 2001 From: Alek Ratzloff Date: Sun, 8 Dec 2019 16:13:16 -0500 Subject: [PATCH] Add day07 Signed-off-by: Alek Ratzloff --- day07/.gitignore | 2 + day07/Cargo.lock | 6 ++ day07/Cargo.toml | 9 ++ day07/src/data.rs | 91 +++++++++++++++++ day07/src/main.rs | 153 +++++++++++++++++++++++++++++ day07/src/permute.rs | 30 ++++++ day07/src/vm.rs | 225 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 516 insertions(+) create mode 100644 day07/.gitignore create mode 100644 day07/Cargo.lock create mode 100644 day07/Cargo.toml create mode 100644 day07/src/data.rs create mode 100644 day07/src/main.rs create mode 100644 day07/src/permute.rs create mode 100644 day07/src/vm.rs diff --git a/day07/.gitignore b/day07/.gitignore new file mode 100644 index 0000000..871d5ba --- /dev/null +++ b/day07/.gitignore @@ -0,0 +1,2 @@ +target/ +*.txt diff --git a/day07/Cargo.lock b/day07/Cargo.lock new file mode 100644 index 0000000..13772db --- /dev/null +++ b/day07/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "day07" +version = "0.1.0" + diff --git a/day07/Cargo.toml b/day07/Cargo.toml new file mode 100644 index 0000000..aae3f64 --- /dev/null +++ b/day07/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day07" +version = "0.1.0" +authors = ["Alek Ratzloff "] +edition = "2018" + +[dependencies] +#permutation = "0.2.5" +#permutate = "0.3.2" diff --git a/day07/src/data.rs b/day07/src/data.rs new file mode 100644 index 0000000..0de10fb --- /dev/null +++ b/day07/src/data.rs @@ -0,0 +1,91 @@ +use std::{ + collections::VecDeque, + io::{self, Write}, +}; + +pub type Queue = VecDeque; + +pub trait IntSource { + fn next(&mut self) -> Option; +} + +pub trait IntSink { + fn feed(&mut self, value: isize); +} + +pub struct StdinSource<'s> { + in_count: usize, + prompt: &'s str, +} + +impl IntSource for StdinSource<'_> { + fn next(&mut self) -> Option { + let mut line = String::new(); + self.in_count += 1; + loop { + print!("{} {} ", self.in_count, self.prompt); + io::stdout().flush().unwrap(); + io::stdin() + .read_line(&mut line) + .expect("could not read line"); + if line.is_empty() { + break None; + } + let parsed = line.trim().parse::(); + if let Ok(value) = parsed { + break Some(value); + } + } + } +} + +impl IntSink for Queue { + fn feed(&mut self, int: isize) { + self.push_back(int); + } +} + +impl IntSource for Queue { + fn next(&mut self) -> Option { + self.pop_front() + } +} + +pub trait IntQueue: IntSink + IntSource {} + +impl IntQueue for T {} + +pub struct IoPair<'io, So, Si>(&'io mut So, &'io mut Si) +where + So: IntSource + 'static, + Si: IntSink + 'static; + +impl<'io, So, Si> IoPair<'io, So, Si> +where + So: IntSource + 'static, + Si: IntSink + 'static, +{ + pub fn new(source: &'io mut So, sink: &'io mut Si) -> Self { + IoPair(source, sink) + } +} + +impl<'io, So, Si> IntSink for IoPair<'io, So, Si> +where + So: IntSource + 'static, + Si: IntSink + 'static, +{ + fn feed(&mut self, value: isize) { + self.1.feed(value); + } +} + +impl<'io, So, Si> IntSource for IoPair<'io, So, Si> +where + So: IntSource + 'static, + Si: IntSink + 'static, +{ + fn next(&mut self) -> Option { + self.0.next() + } +} diff --git a/day07/src/main.rs b/day07/src/main.rs new file mode 100644 index 0000000..3686e4a --- /dev/null +++ b/day07/src/main.rs @@ -0,0 +1,153 @@ +mod data; +mod permute; +mod vm; + +use std::{cell::RefCell, collections::VecDeque, env, fs, io, process}; + +use crate::data::*; +use crate::vm::*; +use permute::*; + +type Result = std::result::Result>; + +#[allow(unused_macros)] +macro_rules! todo { + ($($tt:tt)*) => { unimplemented!($($tt)*) }; +} + +fn parse_args() -> io::Result<(String, String)> { + let args: Vec = env::args().collect(); + if args.len() <= 2 || !(&["part1", "part2"]).contains(&args[1].as_str()) { + println!( + "usage: {} [part1|part2] input.txt", + args.get(0).map(String::as_str).unwrap_or("Day05") + ); + process::exit(1); + } else { + Ok((args[1].to_string(), fs::read_to_string(&args[2])?)) + } +} + +fn main() -> Result<()> { + let (part, input_string) = parse_args()?; + let program: Vec<_> = input_string + .trim() + .split(",") + .filter(|s| !s.is_empty()) + .map(|n| n.parse::().unwrap()) + .collect(); + + if part == "part1" { + part1(&program); + } else if part == "part2" { + part2(&program); + } + Ok(()) +} + +fn part1(program: &Vec) { + let (value, seq) = permutations(5) + .map(|seq| { + let value = run_input_seq(seq.as_slice(), program); + (value, seq) + }) + .max_by_key(|(v, _)| *v) + .unwrap(); + + println!("Part 1: Max value = {} with sequence = {:?}", value, seq); +} + +fn part2(program: &Vec) { + let (value, seq) = permutations(5) + .map(|seq| { + let seq: Vec<_> = seq.into_iter().map(|v| v + 5).collect(); + let value = run_input_feedback_seq(seq.as_slice(), program); + (value, seq) + }) + .max_by_key(|(v, _)| *v) + .unwrap(); + //let seq = &[9, 8, 7, 6, 5]; + //let seq = &[9, 7, 8, 5, 6]; + //let value = run_input_feedback_seq(seq, program); + println!("Part 2: Max value = {} with sequence = {:?}", value, seq); +} + +fn run_input_seq(seq: &[usize], program: &Vec) -> isize { + let mut result = 0; + for n in seq { + let mut program = Program::new(program.clone()); + let mut io_feed = VecDeque::default(); + + io_feed.feed(*n as isize); + io_feed.feed(result); + let program_result = program.run(&mut io_feed); + assert_eq!( + program_result, + HaltResult::Halt, + "program halted waiting for input" + ); + result = io_feed.pop_front().expect("no output"); + assert!(io_feed.is_empty(), "non-empty output feed: {:?}", io_feed); + } + result +} + +fn run_input_feedback_seq(seq: &[usize], program: &Vec) -> isize { + let seq_idx: Vec = seq.iter().copied().map(|n| n - 5).collect(); + + let mut programs = vec![Program::new(program.clone()); seq.len()]; + let mut halts = vec![false; seq.len()]; + let pipes: Vec>> = vec![ + RefCell::new(VecDeque::::new()); seq.len() + ]; + + let first_pipe = seq_idx[0]; + + // Set up initial pipe values that get fed exactly once + for (i, pipe) in pipes.iter().enumerate() { + let mut pipe = pipe.borrow_mut(); + let init = i + 5; + pipe.feed(init as isize); + } + + { + // feed the first program with a value + let mut pipe = pipes[first_pipe].borrow_mut(); + pipe.feed(0); + } + + //println!("{:?}", seq); + while !halts.iter().all(|h| *h) { + for (i, n) in seq.iter().copied().enumerate() { + let in_idx = seq_idx[i]; + assert_eq!(in_idx, n - 5); + + if halts[in_idx] { + continue; + } + + let out_seq_idx = (i + 1) % seq.len(); + let out_idx = seq_idx[out_seq_idx]; + //println!("in: {}, out: {}", seq[in_seq_idx], n); + + let mut in_pipe = pipes[in_idx].borrow_mut(); + let mut out_pipe = pipes[out_idx].borrow_mut(); + + let mut io_pair = IoPair::new(&mut *in_pipe, &mut *out_pipe); + + let halt_result = programs[in_idx].run(&mut io_pair); + match halt_result { + HaltResult::Halt => { + halts[in_idx] = true; + }, + HaltResult::WaitRead => { continue; } + } + } + } + + { + //println!("{:#?}", pipes); + let mut pipe = pipes[first_pipe].borrow_mut(); + pipe.next().expect("no output") + } +} diff --git a/day07/src/permute.rs b/day07/src/permute.rs new file mode 100644 index 0000000..faaa8df --- /dev/null +++ b/day07/src/permute.rs @@ -0,0 +1,30 @@ +// stolen from rosettacode + +pub fn permutations(size: usize) -> Permutations { + Permutations { idxs: (0..size).collect(), swaps: vec![0; size], i: 0 } +} + +pub struct Permutations { + idxs: Vec, + swaps: Vec, + i: usize, +} + +impl Iterator for Permutations { + type Item = Vec; + + fn next(&mut self) -> Option { + if self.i > 0 { + loop { + if self.i >= self.swaps.len() { return None; } + if self.swaps[self.i] < self.i { break; } + self.swaps[self.i] = 0; + self.i += 1; + } + self.idxs.swap(self.i, (self.i & 1) * self.swaps[self.i]); + self.swaps[self.i] += 1; + } + self.i = 1; + Some(self.idxs.clone()) + } +} diff --git a/day07/src/vm.rs b/day07/src/vm.rs new file mode 100644 index 0000000..7714e1b --- /dev/null +++ b/day07/src/vm.rs @@ -0,0 +1,225 @@ +use crate::data::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HaltResult { + Halt, + WaitRead, +} + +#[derive(Debug, Clone)] +pub struct Program { + program: Vec, + pc: usize, +} + +impl Program { + pub fn new(program: Vec) -> Self { + Program { program, pc: 0 } + } + + fn get(&self, param: Param) -> isize { + param.get(&self.program) + } + + fn set(&mut self, addr: usize, what: isize) { + self.program[addr] = what; + } + + pub fn run(&mut self, value_queue: &mut dyn IntQueue) -> HaltResult { + loop { + let op = Op::from(&self.program[self.pc..]); + let mut next_pc = self.pc + op.len(); + match op { + Op::Add { in1, in2, out } => { + let v1 = self.get(in1); + let v2 = self.get(in2); + self.set(out, v1 + v2); + } + Op::Mul { in1, in2, out } => { + let v1 = self.get(in1); + let v2 = self.get(in2); + self.set(out, v1 * v2); + } + Op::Read(pos) => { + if let Some(value) = value_queue.next() { + self.set(pos, value); + } else { + // PC is not incremented, so this should do the read again + break HaltResult::WaitRead; + } + } + Op::Write(pos) => { + let value = self.get(pos); + value_queue.feed(value); + } + Op::JumpTrue { test, jump } => { + let value = self.get(test); + if value != 0 { + let addr = self.get(jump); + assert!(addr >= 0, "invalid jump address: {}", addr); + next_pc = addr as usize; + } + } + Op::JumpFalse { test, jump } => { + let value = self.get(test); + if value == 0 { + let addr = self.get(jump); + assert!(addr >= 0, "invalid jump address: {}", addr); + next_pc = addr as usize; + } + } + Op::LessThan { in1, in2, out } => { + let v1 = self.get(in1); + let v2 = self.get(in2); + + self.set(out, (v1 < v2) as isize); + } + Op::Equals { in1, in2, out } => { + let v1 = self.get(in1); + let v2 = self.get(in2); + + self.set(out, (v1 == v2) as isize); + } + Op::Halt => { + self.pc = next_pc; + break HaltResult::Halt; + } + } + self.pc = next_pc; + } + } +} + +#[derive(Debug, Clone, Copy)] +enum Param { + Pos(usize), + Imm(isize), +} + +impl Param { + fn get(&self, program: &Vec) -> isize { + match self { + Param::Pos(p) => program[*p], + Param::Imm(v) => *v, + } + } +} + +#[derive(Debug, Clone, Copy)] +enum Op { + Add { in1: Param, in2: Param, out: usize }, + Mul { in1: Param, in2: Param, out: usize }, + Read(usize), + Write(Param), + JumpTrue { test: Param, jump: Param }, + JumpFalse { test: Param, jump: Param }, + LessThan { in1: Param, in2: Param, out: usize }, + Equals { in1: Param, in2: Param, out: usize }, + Halt, +} + +fn param_mode(op: usize, param: u32) -> usize { + let mode = op / 100; + (mode / (10usize.pow(param))) & 1 +} + +fn make_param(op: usize, param: u32, value: isize) -> Param { + let mode = param_mode(op, param); + match mode { + 0 => { + assert!( + value >= 0, + "invalid position parameter for mode 0 op={} param={} value={}", + op, + param, + value + ); + Param::Pos(value as usize) + } + 1 => Param::Imm(value), + _ => panic!("invalid mode {} for op={} param={}", mode, op, param), + } +} + +impl Op { + fn len(&self) -> usize { + match self { + Op::Add { .. } | Op::Mul { .. } | Op::LessThan { .. } | Op::Equals { .. } => 4, + Op::JumpTrue { .. } | Op::JumpFalse { .. } => 3, + Op::Read(_) | Op::Write(_) => 2, + Op::Halt => 1, + } + } +} + +impl From<&'_ [isize]> for Op { + fn from(other: &'_ [isize]) -> Self { + let op = other[0] as usize; + match op % 100 { + 1 => { + assert!( + other[3] >= 0, + "invalid output parameter for op={} output={}", + op, + other[3] + ); + Op::Add { + in1: make_param(op, 0, other[1]), + in2: make_param(op, 1, other[2]), + out: other[3] as usize, + } + } + 2 => { + assert!( + other[3] >= 0, + "invalid output parameter for op={} output={}", + op, + other[3] + ); + Op::Mul { + in1: make_param(op, 0, other[1]), + in2: make_param(op, 1, other[2]), + out: other[3] as usize, + } + } + 3 => Op::Read(other[1] as usize), + 4 => Op::Write(make_param(op, 0, other[1])), + 5 => Op::JumpTrue { + test: make_param(op, 0, other[1]), + jump: make_param(op, 1, other[2]), + }, + 6 => Op::JumpFalse { + test: make_param(op, 0, other[1]), + jump: make_param(op, 1, other[2]), + }, + 7 => { + assert!( + other[3] >= 0, + "invalid output parameter for op={} output={}", + op, + other[3] + ); + Op::LessThan { + in1: make_param(op, 0, other[1]), + in2: make_param(op, 1, other[2]), + out: other[3] as usize, + } + } + 8 => { + assert!( + other[3] >= 0, + "invalid output parameter for op={} output={}", + op, + other[3] + ); + Op::Equals { + in1: make_param(op, 0, other[1]), + in2: make_param(op, 1, other[2]), + out: other[3] as usize, + } + } + 99 => Op::Halt, + op => panic!("invalid op: {}", op), + } + } +}