Add day 18 part 2 solution

This commit is contained in:
Wesley Moore 2017-12-23 22:30:10 +11:00
parent 3e43a1c38f
commit dd4d9a7f6f
2 changed files with 98 additions and 26 deletions

View file

@ -40,3 +40,32 @@ At the time the recover operation is executed, the frequency of the last sound p
What is the value of the recovered frequency (the value of the most recently played sound) the first time a rcv instruction is executed with a non-zero value?
--- Part Two ---
As you congratulate yourself for a job well done, you notice that the documentation has been on the back of the tablet this entire time. While you actually got most of the instructions correct, there are a few key differences. This assembly code isn't about sound at all - it's meant to be run twice at the same time.
Each running copy of the program has its own set of registers and follows the code independently - in fact, the programs don't even necessarily run at the same speed. To coordinate, they use the send (snd) and receive (rcv) instructions:
snd X sends the value of X to the other program. These values wait in a queue until that program is ready to receive them. Each program has its own message queue, so a program can never receive a message it sent.
rcv X receives the next value and stores it in register X. If no values are in the queue, the program waits for a value to be sent to it. Programs do not continue to the next instruction until they have received a value. Values are received in the order they are sent.
Each program also has its own program ID (one 0 and the other 1); the register p should begin with this value.
For example:
snd 1
snd 2
snd p
rcv a
rcv b
rcv c
rcv d
Both programs begin by sending three values to the other. Program 0 sends 1, 2, 0; program 1 sends 1, 2, 1. Then, each program receives a value (both 1) and stores it in a, receives another value (both 2) and stores it in b, and then each receives the program ID of the other program (program 0 receives 1; program 1 receives 0) and stores it in c. Each program now sees a different value in its own copy of register c.
Finally, both programs try to rcv a fourth time, but no data is waiting for either of them, and they reach a deadlock. When this happens, both programs terminate.
It should be noted that it would be equally valid for the programs to run at different speeds; for example, program 0 might have sent all three values and then stopped at the first rcv before program 1 executed even its first instruction.
Once both of your programs have terminated (regardless of what caused them to do so), how many times did program 1 send a value?

View file

@ -2,6 +2,10 @@ use std::fs::File;
use std::io::{BufRead, BufReader};
use std::str::FromStr;
use std::collections::HashMap;
use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError};
use std::thread;
use std::sync::Arc;
use std::time::Duration;
type Register = char;
@ -78,32 +82,36 @@ impl Program {
// TODO: Machine executes programs and instructions
#[derive(Debug)]
struct Machine {
pid: usize,
registers: HashMap<char, isize>,
pc: isize,
last_played: isize,
last_received: isize,
send_count: usize,
}
impl Machine {
pub fn new() -> Machine {
pub fn new(pid: usize) -> Machine {
let mut registers = HashMap::new();
registers.insert('p', pid as isize);
Machine {
registers: HashMap::new(),
pid,
registers,
pc: 0,
last_played: 0,
last_received: 0,
send_count: 0,
}
}
pub fn run(&mut self, program: &Program) {
pub fn run(&mut self, program: &Program, send: Sender<isize>, recv: Receiver<isize>) {
loop {
if self.pc < 0 || self.pc >= program.0.len() as isize {
println!("pid {}: send_count = {}", self.pid, self.send_count);
break;
}
let instruction = &program.0[self.pc as usize];
self.execute(instruction);
if self.last_received != 0 {
// println!("pid {}: {:?}", self.pid, instruction);
if let Err(_err) = self.execute(instruction, &send, &recv) {
println!("pid {}: send_count = {}", self.pid, self.send_count);
break;
}
}
@ -117,15 +125,10 @@ impl Machine {
self.registers.insert(register, value);
}
fn execute(&mut self, instruction: &Instruction) {
fn execute(&mut self, instruction: &Instruction, send: &Sender<isize>, recv: &Receiver<isize>) -> Result<(), RecvTimeoutError> {
use Instruction::*;
// TODO; Increment pc
match *instruction {
Snd(ref op) => {
self.last_played = op.value(self);
}
Set(Operand::Register(reg), ref val) => {
let value = val.value(self);
self.set(reg, value);
@ -145,11 +148,16 @@ impl Machine {
let value = val.value(self);
self.set(reg, reg_value % value);
}
Rcv(ref op) => {
Snd(ref op) => {
let value = op.value(self);
if value != 0 {
self.last_received = self.last_played;
}
send.send(value).expect("error sending");
self.send_count += 1;
// println!("pid {}: sent {}", self.pid, value);
}
Rcv(Operand::Register(reg)) => {
let value = recv.recv_timeout(Duration::from_secs(5))?;
// println!("pid {}: recv {}", self.pid, value);
self.set(reg, value);
}
Jgz(_, _) => { /* handled below */ }
_ => panic!("bad argument")
@ -161,7 +169,7 @@ impl Machine {
let x_value = x.value(self);
let offset_value = offset.value(self);
if x_value != 0 {
if x_value > 0 {
self.pc += offset_value;
}
else {
@ -170,14 +178,49 @@ impl Machine {
}
_ => self.pc += 1
};
Ok(())
}
}
fn main() {
let program = Program::load("input");
let mut machine = Machine::new();
fn run(program: Program) {
let program = Arc::new(program);
let mut machine0 = Machine::new(0);
let mut machine1 = Machine::new(1);
machine.run(&program);
let (send0, recv1) = channel();
let (send1, recv0) = channel();
println!("{:?}", machine);
let program0 = Arc::clone(&program);
let thread0 = thread::spawn(move || {
machine0.run(&program0, send0, recv0);
});
let program1 = Arc::clone(&program);
let thread1 = thread::spawn(move || {
machine1.run(&program1, send1, recv1);
});
thread0.join().unwrap();
thread1.join().unwrap();
}
fn main() {
run(Program::load("input"));
}
#[test]
fn test_part_two() {
let input = r"snd 1
snd 2
snd p
rcv a
rcv b
rcv c
rcv d";
let program = input.lines()
.map(|line| Instruction::new(line.as_ref()))
.collect::<Vec<Instruction>>();
run(Program(program));
}