Skip to content

rust: add solution for year 2017, day 18 #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ fn imain() -> i32 {
(2017, 17, 1) => Ok(aoc::year2017::day17::part1),
(2017, 17, 2) => Ok(aoc::year2017::day17::part2),
(2017, 18, 1) => Ok(aoc::year2017::day18::part1),
(2017, 18, 2) => Ok(aoc::year2017::day18::part2),
(2017, 19, 1) => Ok(aoc::year2017::day19::part1),
(2017, 19, 2) => Ok(aoc::year2017::day19::part2),
// Year 2018.
Expand Down
282 changes: 143 additions & 139 deletions rust/src/year2017/day18.rs
Original file line number Diff line number Diff line change
@@ -1,188 +1,185 @@
use std::collections::HashMap;
use std::io;
use std::collections::{HashMap, VecDeque};
use std::io::{BufRead, BufReader, Read};
use std::str::FromStr;

use crate::base::Part;

pub fn part1(r: &mut dyn io::Read) -> Result<String, String> {
pub fn part1(r: &mut dyn Read) -> Result<String, String> {
solve(r, Part::One)
}

fn solve(r: &mut dyn io::Read, _part: Part) -> Result<String, String> {
let mut input = String::new();
r.read_to_string(&mut input).map_err(|e| e.to_string())?;
let instructions = parse_input(&input);
let mut processor = Processor::from_instructions(&instructions);
while processor.recovered_frequency.is_none() {
processor.perform_instruction();
}
let recovered_frequency = processor.recovered_frequency.unwrap();
Ok(recovered_frequency.to_string())
pub fn part2(r: &mut dyn Read) -> Result<String, String> {
solve(r, Part::Two)
}

fn parse_input(input: &str) -> Vec<Instruction> {
input
fn solve(r: &mut dyn Read, part: Part) -> Result<String, String> {
let instructions = BufReader::new(r)
.lines()
.map(Instruction::from_str)
.map(Result::unwrap)
.collect()
.map(|line| line.expect("input line read failed"))
.map(|ref line| Instruction::from_str(line))
.map(|i| i.expect("instruction parsing failed"))
.collect::<Vec<Instruction>>();
let mut vm0 = VM::new(&instructions);
let mut outs0 = vm0.run();
if part == Part::One {
return Ok(outs0.back().unwrap().to_string());
}
let mut vm0_sent = outs0.len();
let mut vm1 = VM::new(&instructions);
*vm1.register('p') = 1;
vm1.inputs.append(&mut outs0);
let mut vm1_sent = 0;
let mut vm0_running = false;
loop {
let (current, sent, next) = match vm0_running {
true => (&mut vm0, &mut vm0_sent, &mut vm1),
false => (&mut vm1, &mut vm1_sent, &mut vm0),
};
let mut outs = current.run();
if outs.len() == 0 {
break;
}
*sent += outs.len();
next.inputs.append(&mut outs);
vm0_running = !vm0_running;
}
Ok(vm1_sent.to_string())
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct Processor {
registers: HashMap<char, i64>,
instruction_pointer: usize,
struct VM {
instructions: Vec<Instruction>,
last_frequency: Option<i64>,
recovered_frequency: Option<i64>,
registers: HashMap<char, i64>,
inputs: VecDeque<i64>,
pc: isize,
}

impl Processor {
fn from_instructions(instructions: &[Instruction]) -> Processor {
let registers = initialize_registers(instructions);
let instruction_pointer = 0;
Processor {
registers,
instruction_pointer,
impl VM {
fn new(instructions: &[Instruction]) -> Self {
VM {
instructions: instructions.to_vec(),
last_frequency: None,
recovered_frequency: None,
registers: HashMap::new(),
inputs: VecDeque::new(),
pc: 0,
}
}

fn op_to_value(&self, op: Operand) -> i64 {
match op {
Operand::Register(reg) => self.registers[&reg],
Operand::Literal(val) => val,
fn run(&mut self) -> VecDeque<i64> {
let mut outs = VecDeque::new();
while let Some(opt_out) = self.step() {
if let Some(i) = opt_out {
outs.push_back(i);
}
}
outs
}

fn perform_instruction(&mut self) {
let instruction = self.instructions[self.instruction_pointer];
if let Instruction::Jgz(op1, op2) = instruction {
let val1 = self.op_to_value(op1);
let val2 = self.op_to_value(op2);
if val1 > 0 {
if val2 > 0 {
self.instruction_pointer += val2 as usize;
} else {
self.instruction_pointer -= val2.abs() as usize;
}
} else {
self.instruction_pointer += 1;
fn step(&mut self) -> Option<Option<i64>> {
if self.pc as usize >= self.instructions.len() {
return None;
}
let mut new_pc = self.pc + 1;
let mut out = None;
match self.instructions[self.pc as usize] {
Instruction::Snd(op) => out = Some(self.eval_op(op)),
Instruction::Set(op1, op2) => *self.must_register(op1) = self.eval_op(op2),
Instruction::Add(op1, op2) => *self.must_register(op1) += self.eval_op(op2),
Instruction::Mul(op1, op2) => *self.must_register(op1) *= self.eval_op(op2),
Instruction::Mod(op1, op2) => *self.must_register(op1) %= self.eval_op(op2),
Instruction::Rcv(op) => {
match self.inputs.pop_front() {
Some(i) => *self.must_register(op) = i,
None => return None,
};
}
} else {
match instruction {
Instruction::Sound(op) => self.last_frequency = Some(self.op_to_value(op)),
Instruction::Set(reg, op) => {
*self.registers.get_mut(&reg).unwrap() = self.op_to_value(op)
}
Instruction::Add(reg, op) => {
*self.registers.get_mut(&reg).unwrap() += self.op_to_value(op)
Instruction::Jgz(op1, op2) => {
if self.eval_op(op1) > 0 {
new_pc = self.pc + self.eval_op(op2) as isize;
}
Instruction::Mul(reg, op) => {
*self.registers.get_mut(&reg).unwrap() *= self.op_to_value(op)
}
Instruction::Mod(reg, op) => {
*self.registers.get_mut(&reg).unwrap() %= self.op_to_value(op)
}
Instruction::Recover(op) => {
if self.op_to_value(op) > 0 {
self.recovered_frequency = Some(self.last_frequency.unwrap());
}
}
_ => {}
}
self.instruction_pointer += 1;
}
};
self.pc = new_pc;
Some(out)
}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum Operand {
Register(char),
Literal(i64),
}
fn eval_op(&mut self, op: Operand) -> i64 {
match op {
Operand::Integer(i) => i,
Operand::Register(c) => *self.register(c),
}
}

impl FromStr for Operand {
type Err = String;
fn register(&mut self, c: char) -> &mut i64 {
self.registers.entry(c).or_insert(0)
}

fn from_str(s: &str) -> Result<Operand, String> {
let first_char = s.chars().next().unwrap();
if first_char.is_digit(10) || first_char == '-' {
Ok(Operand::Literal(i64::from_str(s).unwrap()))
fn must_register(&mut self, op: Operand) -> &mut i64 {
if let Operand::Register(c) = op {
self.register(c)
} else {
Ok(Operand::Register(char::from_str(s).unwrap()))
panic!("non-register operand {:?}", op)
}
}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug)]
enum Instruction {
Sound(Operand),
Set(char, Operand),
Add(char, Operand),
Mul(char, Operand),
Mod(char, Operand),
Recover(Operand),
Snd(Operand),
Set(Operand, Operand),
Add(Operand, Operand),
Mul(Operand, Operand),
Mod(Operand, Operand),
Rcv(Operand),
Jgz(Operand, Operand),
}

impl FromStr for Instruction {
type Err = String;

fn from_str(s: &str) -> Result<Instruction, String> {
let parts: Vec<&str> = s.split(' ').collect();
if parts.len() == 2 {
let operand = Operand::from_str(parts[1])?;
match parts[0] {
"snd" => Ok(Instruction::Sound(operand)),
"rcv" => Ok(Instruction::Recover(operand)),
_ => Err(format!("invalid instruction: {}", s)),
}
} else if parts.len() == 3 {
let op2 = Operand::from_str(parts[2])?;
if parts[0] == "jgz" {
let op1 = Operand::from_str(parts[1])?;
Ok(Instruction::Jgz(op1, op2))
} else {
let op1 = char::from_str(parts[1]).map_err(|e| e.to_string())?;
match parts[0] {
"set" => Ok(Instruction::Set(op1, op2)),
"add" => Ok(Instruction::Add(op1, op2)),
"mul" => Ok(Instruction::Mul(op1, op2)),
"mod" => Ok(Instruction::Mod(op1, op2)),
_ => Err(format!("invalid instruction: {}", s)),
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split(" ").collect::<Vec<&str>>();
if parts.len() == 0 {
return Err("empty instruction".to_string());
}
if parts.len() == 1 {
return Err(format!("missing operands: {}", s));
}
let op = parts[0];
let operand1 = Operand::from_str(parts[1])?;
match op {
"snd" => Ok(Instruction::Snd(operand1)),
"rcv" => Ok(Instruction::Rcv(operand1)),
"set" | "add" | "mul" | "mod" | "jgz" if parts.len() == 3 => {
let operand2 = Operand::from_str(parts[2])?;
Ok(match op {
"set" => Instruction::Set(operand1, operand2),
"add" => Instruction::Add(operand1, operand2),
"mul" => Instruction::Mul(operand1, operand2),
"mod" => Instruction::Mod(operand1, operand2),
"jgz" => Instruction::Jgz(operand1, operand2),
_ => unreachable!(),
})
}
} else {
Err(format!("invalid instruction: {}", s))
_ => Err(format!("invalid op: {}", op)),
}
}
}

fn initialize_registers(instructions: &[Instruction]) -> HashMap<char, i64> {
let mut map = HashMap::new();
for &instruction in instructions {
let operands = match instruction {
Instruction::Sound(op) => vec![op],
Instruction::Set(reg, op)
| Instruction::Add(reg, op)
| Instruction::Mul(reg, op)
| Instruction::Mod(reg, op) => {
map.insert(reg, 0);
vec![op]
}
Instruction::Recover(op) => vec![op],
Instruction::Jgz(op1, op2) => vec![op1, op2],
};
for op in operands.into_iter() {
if let Operand::Register(reg) = op {
map.insert(reg, 0);
}
#[derive(Clone, Copy, Debug)]
enum Operand {
Integer(i64),
Register(char),
}

impl FromStr for Operand {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(i) = i64::from_str(s) {
return Ok(Operand::Integer(i));
}
if let Some(c) = s.chars().next() {
return Ok(Operand::Register(c));
}
Err(format!("invalid operand: {}", s))
}
map
}

#[cfg(test)]
Expand All @@ -193,7 +190,14 @@ mod tests {
mod part1 {
use super::*;

test!(example, file "testdata/day18/ex", "4", part1);
test!(example, file "testdata/day18/p1ex", "4", part1);
test!(actual, file "../../../inputs/2017/18", "3188", part1);
}

mod part2 {
use super::*;

test!(example, file "testdata/day18/p2ex", "3", part2);
test!(actual, file "../../../inputs/2017/18", "7112", part2);
}
}
7 changes: 7 additions & 0 deletions rust/src/year2017/testdata/day18/p2ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
snd 1
snd 2
snd p
rcv a
rcv b
rcv c
rcv d