use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
solution_printer!(7, print_solution, input_generator, INPUT, solve_part_1, solve_part_2);
pub const INPUT: &str = include_str!("../input/2019/day7.txt");
pub fn solve_part_1 (input_memory: &Memory) -> Intcode
{
find_max_thruster_signal_serial_amps(input_memory).0
}
pub type PhaseSettings = [Intcode; NUM_ELEMS_IN_A_PERMUTATION];
pub fn find_max_thruster_signal_serial_amps (memory: &Memory) -> (Intcode, PhaseSettings)
{
PermutationsIterator::new([0,1,2,3,4]).map(
|phase_settings| (run_amplifiers_serially(memory, &phase_settings), phase_settings)).max().unwrap()
}
pub struct PermutationsIterator<T: Copy>
{
next_permutation: [T; NUM_ELEMS_IN_A_PERMUTATION],
c: [usize; NUM_ELEMS_IN_A_PERMUTATION],
i: usize,
}
const NUM_ELEMS_IN_A_PERMUTATION: usize = 5;
impl<T: Copy> PermutationsIterator<T>
{
pub fn new (initial_permutation: [T; NUM_ELEMS_IN_A_PERMUTATION]) -> Self
{
Self
{
next_permutation: initial_permutation,
c: [0; NUM_ELEMS_IN_A_PERMUTATION],
i: 0,
}
}
}
impl<T: Copy> std::iter::Iterator for PermutationsIterator<T>
{
type Item = [T; NUM_ELEMS_IN_A_PERMUTATION];
fn next (&mut self) -> Option<Self::Item>
{
let curr = self.next_permutation;
while self.i < NUM_ELEMS_IN_A_PERMUTATION
{
if self.c[self.i] < self.i
{
if self.i % 2 == 0
{
self.next_permutation.swap(0, self.i);
}
else
{
self.next_permutation.swap(self.c[self.i], self.i);
}
self.c[self.i] += 1;
self.i = 0;
return Some(curr);
}
else
{
self.c[self.i] = 0;
self.i += 1;
}
}
if self.i == NUM_ELEMS_IN_A_PERMUTATION
{
self.i += 1;
Some(curr)
}
else
{
None
}
}
}
pub fn run_amplifiers_serially (memory: &Memory, phase_settings: &[Intcode; NUM_ELEMS_IN_A_PERMUTATION]) -> Intcode
{
let output = run_intcode_program(memory, &[phase_settings[0], 0]).outputs_port_b;
let output = run_intcode_program(memory, &[phase_settings[1], output[0]]).outputs_port_b;
let output = run_intcode_program(memory, &[phase_settings[2], output[0]]).outputs_port_b;
let output = run_intcode_program(memory, &[phase_settings[3], output[0]]).outputs_port_b;
let output = run_intcode_program(memory, &[phase_settings[4], output[0]]).outputs_port_b;
output[0]
}
pub type Intcode = i32;
pub type Memory = Vec<Intcode>;
pub type InputsPortA = [Intcode];
pub type OutputsPortB = Vec<Intcode>;
pub fn input_generator (input: &str) -> Memory
{
input.trim_end_matches('\n').split(',').map(|intcode_str| intcode_str.parse::<Intcode>().unwrap()).collect()
}
pub struct EmulationFinished
{
pub memory: Memory,
pub outputs_port_b: OutputsPortB,
}
enum Instruction
{
Add,
Multiply,
ReadFromPortA,
WriteToPortB,
JumpIfTrue,
JumpIfFalse,
LessThan,
Equals,
Exit,
}
pub fn run_intcode_program (memory: &Memory, inputs_port_a: &InputsPortA) -> EmulationFinished
{
let mut memory = memory.clone();
let mut pc = 0;
let mut outputs_port_b: OutputsPortB = vec![];
let mut inputs_port_a_iter = inputs_port_a.iter();
loop
{
let opcode = memory[pc];
let (instruction, input_param_values_in_mem, output_param_value_in_mem) = match opcode % 100
{
1 => (Instruction::Add, &memory[pc+1..=pc+2], Some(memory[pc+3])),
2 => (Instruction::Multiply, &memory[pc+1..=pc+2], Some(memory[pc+3])),
3 => (Instruction::ReadFromPortA, &[][..], Some(memory[pc+1])),
4 => (Instruction::WriteToPortB, &memory[pc+1..=pc+1], None),
5 => (Instruction::JumpIfTrue, &memory[pc+1..=pc+2], None),
6 => (Instruction::JumpIfFalse, &memory[pc+1..=pc+2], None),
7 => (Instruction::LessThan, &memory[pc+1..=pc+2], Some(memory[pc+3])),
8 => (Instruction::Equals, &memory[pc+1..=pc+2], Some(memory[pc+3])),
99 => (Instruction::Exit, &[][..], None),
_ => panic!("Invalid opcode {} at position {}", opcode, pc),
};
let mut input_param_modes = opcode / 100;
let mut input_param_values = Vec::from(input_param_values_in_mem);
for input_param_value in input_param_values.iter_mut()
{
if input_param_modes & 1 == 0 { *input_param_value = memory[*input_param_value as usize]; }
input_param_modes /= 10;
}
assert_eq!(input_param_modes, 0);
pc += input_param_values.len() + output_param_value_in_mem.is_some() as usize + 1;
match instruction
{
Instruction::Add => memory[output_param_value_in_mem.unwrap() as usize] = input_param_values[0] + input_param_values[1],
Instruction::Multiply => memory[output_param_value_in_mem.unwrap() as usize] = input_param_values[0] * input_param_values[1],
Instruction::ReadFromPortA => memory[output_param_value_in_mem.unwrap() as usize] = *inputs_port_a_iter.next().unwrap(),
Instruction::WriteToPortB => outputs_port_b.push(input_param_values[0]),
Instruction::JumpIfTrue => if input_param_values[0] != 0 { pc = input_param_values[1] as usize; },
Instruction::JumpIfFalse => if input_param_values[0] == 0 { pc = input_param_values[1] as usize; },
Instruction::LessThan => memory[output_param_value_in_mem.unwrap() as usize] = (input_param_values[0] < input_param_values[1]) as Intcode,
Instruction::Equals => memory[output_param_value_in_mem.unwrap() as usize] = (input_param_values[0] == input_param_values[1]) as Intcode,
Instruction::Exit => break,
}
}
EmulationFinished
{
memory,
outputs_port_b,
}
}
pub fn solve_part_2 (input_memory: &Memory) -> Intcode
{
find_max_thruster_signal_feedback_loop_amps(input_memory).0
}
pub fn find_max_thruster_signal_feedback_loop_amps (memory: &Memory) -> (Intcode, PhaseSettings)
{
PermutationsIterator::new([5,6,7,8,9]).map(
|phase_settings| (run_amplifier_feedback_loop(memory, &phase_settings), phase_settings)).max().unwrap()
}
pub fn run_amplifier_feedback_loop (memory: &Memory, phase_settings: &[Intcode; NUM_ELEMS_IN_A_PERMUTATION]) -> Intcode
{
let (amp_a_output_tx, amp_a_output_rx) = mpsc::channel();
let (amp_b_output_tx, amp_b_output_rx) = mpsc::channel();
let (amp_c_output_tx, amp_c_output_rx) = mpsc::channel();
let (amp_d_output_tx, amp_d_output_rx) = mpsc::channel();
let (amp_e_output_tx, amp_e_output_rx) = mpsc::channel();
let (probe_output_tx, probe_output_rx) = mpsc::channel();
let (amp_a_input_tx, amp_a_input_rx) = (probe_output_tx.clone(), probe_output_rx);
let (amp_b_input_tx, amp_b_input_rx) = (amp_a_output_tx.clone(), amp_a_output_rx);
let (amp_c_input_tx, amp_c_input_rx) = (amp_b_output_tx.clone(), amp_b_output_rx);
let (amp_d_input_tx, amp_d_input_rx) = (amp_c_output_tx.clone(), amp_c_output_rx);
let (amp_e_input_tx, amp_e_input_rx) = (amp_d_output_tx.clone(), amp_d_output_rx);
let probe_input_rx = amp_e_output_rx;
let probe_handle = thread::spawn(move ||
{
let mut most_recent_probe_value;
let mut num_inputs_read_in_probe = 0;
let mut num_outputs_written_in_probe = 0;
loop
{
if let Ok(value) = probe_input_rx.try_recv()
{
most_recent_probe_value = value;
num_inputs_read_in_probe += 1;
match probe_output_tx.send(value)
{
Ok(_) => num_outputs_written_in_probe += 1,
Err(_) => break,
}
}
}
(most_recent_probe_value, num_inputs_read_in_probe, num_outputs_written_in_probe)
});
let memory_a = memory.clone();
let amp_a_handle = thread::spawn(move ||
run_intcode_program_b(memory_a, amp_a_input_rx, amp_a_output_tx));
amp_a_input_tx.send(phase_settings[0]).unwrap();
let memory_b = memory.clone();
let amp_b_handle = thread::spawn(move ||
run_intcode_program_b(memory_b, amp_b_input_rx, amp_b_output_tx));
amp_b_input_tx.send(phase_settings[1]).unwrap();
let memory_c = memory.clone();
let amp_c_handle = thread::spawn(move ||
run_intcode_program_b(memory_c, amp_c_input_rx, amp_c_output_tx));
amp_c_input_tx.send(phase_settings[2]).unwrap();
let memory_d = memory.clone();
let amp_d_handle = thread::spawn(move ||
run_intcode_program_b(memory_d, amp_d_input_rx, amp_d_output_tx));
amp_d_input_tx.send(phase_settings[3]).unwrap();
let memory_e = memory.clone();
let amp_e_handle = thread::spawn(move ||
run_intcode_program_b(memory_e, amp_e_input_rx, amp_e_output_tx));
amp_e_input_tx.send(phase_settings[4]).unwrap();
amp_a_input_tx.send(0).unwrap();
amp_a_handle.join().unwrap();
amp_b_handle.join().unwrap();
amp_c_handle.join().unwrap();
amp_d_handle.join().unwrap();
amp_e_handle.join().unwrap();
probe_handle.join().unwrap().0
}
pub struct EmulationFinishedB
{
pub memory: Memory,
pub pc: usize,
pub num_inputs_read: u32,
pub num_outputs_written: u32,
}
pub fn run_intcode_program_b (mut memory: Memory, inputs_port_a_rx: Receiver<Intcode>, outputs_port_b_tx: Sender<Intcode>) -> EmulationFinishedB
{
let mut pc = 0;
let mut num_inputs_read = 0;
let mut num_outputs_written = 0;
loop
{
let opcode = memory[pc];
let (instruction, input_param_values_in_mem, output_param_value_in_mem) = match opcode % 100
{
1 => (Instruction::Add, &memory[pc+1..=pc+2], Some(memory[pc+3])),
2 => (Instruction::Multiply, &memory[pc+1..=pc+2], Some(memory[pc+3])),
3 => (Instruction::ReadFromPortA, &[][..], Some(memory[pc+1])),
4 => (Instruction::WriteToPortB, &memory[pc+1..=pc+1], None),
5 => (Instruction::JumpIfTrue, &memory[pc+1..=pc+2], None),
6 => (Instruction::JumpIfFalse, &memory[pc+1..=pc+2], None),
7 => (Instruction::LessThan, &memory[pc+1..=pc+2], Some(memory[pc+3])),
8 => (Instruction::Equals, &memory[pc+1..=pc+2], Some(memory[pc+3])),
99 => (Instruction::Exit, &[][..], None),
_ => panic!("Invalid opcode {} at position {}", opcode, pc),
};
let mut input_param_modes = opcode / 100;
let mut input_param_values = Vec::from(input_param_values_in_mem);
for input_param_value in input_param_values.iter_mut()
{
if input_param_modes & 1 == 0 { *input_param_value = memory[*input_param_value as usize]; }
input_param_modes /= 10;
}
assert_eq!(input_param_modes, 0);
pc += input_param_values.len() + output_param_value_in_mem.is_some() as usize + 1;
match instruction
{
Instruction::Add => memory[output_param_value_in_mem.unwrap() as usize] = input_param_values[0] + input_param_values[1],
Instruction::Multiply => memory[output_param_value_in_mem.unwrap() as usize] = input_param_values[0] * input_param_values[1],
Instruction::ReadFromPortA => { memory[output_param_value_in_mem.unwrap() as usize] = inputs_port_a_rx.recv().unwrap(); num_inputs_read += 1; },
Instruction::WriteToPortB => { outputs_port_b_tx.send(input_param_values[0]).unwrap(); num_outputs_written += 1; },
Instruction::JumpIfTrue => if input_param_values[0] != 0 { pc = input_param_values[1] as usize; },
Instruction::JumpIfFalse => if input_param_values[0] == 0 { pc = input_param_values[1] as usize; },
Instruction::LessThan => memory[output_param_value_in_mem.unwrap() as usize] = (input_param_values[0] < input_param_values[1]) as Intcode,
Instruction::Equals => memory[output_param_value_in_mem.unwrap() as usize] = (input_param_values[0] == input_param_values[1]) as Intcode,
Instruction::Exit => break,
}
}
EmulationFinishedB
{
memory,
pc,
num_inputs_read,
num_outputs_written,
}
}