From d617f105ad01f1a02c627a9edd543c478d51c4c9 Mon Sep 17 00:00:00 2001 From: Joshua Cook Date: Mon, 15 Jan 2024 10:27:04 -0500 Subject: [PATCH] Day 20 puzzle 1 --- src/lib.rs | 4 + src/solutions/day20.rs | 350 +++++++++++++++++++++++++++++++++++++++++ src/solutions/mod.rs | 1 + tests/test_day20.rs | 35 +++++ 4 files changed, 390 insertions(+) create mode 100644 src/solutions/day20.rs create mode 100644 tests/test_day20.rs diff --git a/src/lib.rs b/src/lib.rs index eccab2d..9aae442 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,10 @@ pub fn run_day(data_dir: &str, day: &u32) -> Result<(), Error> { solutions::day19::main(data_dir); Ok(()) } + 20 => { + solutions::day20::main(data_dir); + Ok(()) + } // <-- INSERT NEW DAY HERE --> _ => Err(Error::DayNotImplemented(*day)), } diff --git a/src/solutions/day20.rs b/src/solutions/day20.rs new file mode 100644 index 0000000..f600d00 --- /dev/null +++ b/src/solutions/day20.rs @@ -0,0 +1,350 @@ +use crate::data::load; +use std::collections::{HashMap, HashSet, VecDeque}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum PuzzleErr { + #[error("Input parsing error")] + ParseInputError, + #[error("Runtime error")] + RuntimeError, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Pulse { + High, + Low, +} + +#[derive(Debug, Clone)] +struct PulseMsg { + from: String, + to: String, + pulse: Pulse, +} + +trait Receiver { + fn receive(&mut self, in_pulse: &PulseMsg) -> Option>; +} + +#[derive(Debug, Clone)] +struct Broadcast { + name: String, + receivers: Vec, +} + +fn _parse_after_arrow(line: &str) -> Vec { + line.trim() + .split("->") + .nth(1) + .unwrap() + .trim() + .split(", ") + .map(|s| s.to_string()) + .collect::>() +} + +impl Broadcast { + fn new(receivers: &[String]) -> Self { + Broadcast { + name: "broadcaster".to_string(), + receivers: receivers.to_owned(), + } + } + + fn from(line: &str) -> Self { + let receivers = _parse_after_arrow(line); + Self::new(&receivers) + } +} + +impl Receiver for Broadcast { + fn receive(&mut self, in_pulse: &PulseMsg) -> Option> { + Option::Some( + self.receivers + .iter() + .map(|r| PulseMsg { + from: self.name.clone(), + to: r.clone(), + pulse: in_pulse.pulse, + }) + .collect::>(), + ) + } +} + +#[derive(Debug, Clone)] +struct Conjunction { + name: String, + memory: HashMap, + receivers: Vec, +} + +impl Conjunction { + fn new(name: &str, receivers: &[String]) -> Self { + Self { + name: name.to_string(), + memory: HashMap::new(), + receivers: receivers.to_owned(), + } + } + + fn from(line: &str) -> Self { + let name = line.split("->").nth(0).unwrap().replace('&', ""); + let receivers = _parse_after_arrow(line); + Self::new(name.trim(), &receivers) + } + + fn add_input(&mut self, new_input: &str) { + self.memory.insert(new_input.to_string(), Pulse::Low); + } + + fn add_inputs(&mut self, new_inputs: &HashSet<&str>) { + new_inputs.iter().for_each(|i| self.add_input(i)) + } +} + +impl Receiver for Conjunction { + fn receive(&mut self, in_pulse: &PulseMsg) -> Option> { + self.memory.insert(in_pulse.from.clone(), in_pulse.pulse); + let out_pulse = match self.memory.values().all(|p| p == &Pulse::High) { + true => Pulse::Low, + false => Pulse::High, + }; + Option::Some( + self.receivers + .iter() + .map(|r| PulseMsg { + from: self.name.clone(), + to: r.clone(), + pulse: out_pulse, + }) + .collect::>(), + ) + } +} + +#[derive(Debug, Clone)] +struct FlipFlop { + name: String, + state: bool, // true = "on", false = "off" + receivers: Vec, +} + +impl FlipFlop { + fn new(name: &str, receivers: &[String]) -> Self { + Self { + name: name.to_string(), + state: false, + receivers: receivers.to_owned(), + } + } + + fn from(line: &str) -> Self { + let name = line.split("->").nth(0).unwrap().replace('%', ""); + let receivers = _parse_after_arrow(line); + Self::new(name.trim(), &receivers) + } +} + +impl Receiver for FlipFlop { + fn receive(&mut self, in_pulse: &PulseMsg) -> Option> { + log::trace!( + "FlipFlip {} received {:?} pulse.", + self.name, + in_pulse.pulse + ); + if in_pulse.pulse == Pulse::High { + return None; + } + let out_pulse = match self.state { + false => Pulse::High, + true => Pulse::Low, + }; + self.state = !self.state; + Option::Some( + self.receivers + .iter() + .map(|r| PulseMsg { + from: self.name.clone(), + to: r.clone(), + pulse: out_pulse, + }) + .collect::>(), + ) + } +} + +#[derive(Debug, Clone)] +struct Output { + name: String, +} + +impl Output { + fn new() -> Self { + Self { + name: "output".to_string(), + } + } +} + +impl Receiver for Output { + fn receive(&mut self, _: &PulseMsg) -> Option> { + Option::None + } +} + +#[derive(Debug, Clone)] +enum Module { + B(Broadcast), + C(Conjunction), + F(FlipFlop), + O(Output), +} + +fn _parse_input_line(line: &str) -> Result { + if line.starts_with("broadcaster") { + Ok(Module::B(Broadcast::from(line))) + } else if line.starts_with('%') { + Ok(Module::F(FlipFlop::from(line))) + } else if line.starts_with('&') { + Ok(Module::C(Conjunction::from(line))) + } else { + Err(PuzzleErr::ParseInputError) + } +} + +fn parse_input(input: &str) -> Result, PuzzleErr> { + // Parse the individual modules defined on each line. + let mut modules = input + .trim() + .lines() + .map(_parse_input_line) + .collect::, PuzzleErr>>()?; + + // Manually add the `Output` module. + modules.push(Module::O(Output::new())); + + // Convert the vector into a dictionary. + let mut mapping = modules + .into_iter() + .map(|m| { + let x: (String, Module) = match m { + Module::B(ref a) => (a.name.clone(), m), + Module::C(ref a) => (a.name.clone(), m), + Module::F(ref a) => (a.name.clone(), m), + Module::O(ref a) => (a.name.clone(), m), + }; + x + }) + .collect::>(); + + // Get inputs for Conjugation modules. + let mut receiver_connections = HashMap::>::new(); + let duplicate_mapping = mapping.clone(); + for (name, module) in duplicate_mapping.iter() { + let recievers = match module { + Module::B(b) => b.receivers.clone(), + Module::F(f) => f.receivers.clone(), + Module::C(c) => c.receivers.clone(), + _ => Vec::new(), + }; + recievers.iter().for_each(|r| { + receiver_connections + .entry(r.to_string()) + .and_modify(|s| { + s.insert(name.as_str()); + }) + .or_insert(HashSet::from_iter([name.as_str()])); + }); + } + for (receiver, input_mods) in receiver_connections.iter() { + if let Some(Module::C(c)) = mapping.get_mut(receiver) { + c.add_inputs(input_mods) + } + } + Ok(mapping) +} + +struct PulseCounter { + low: u32, + high: u32, +} + +impl PulseCounter { + fn new() -> Self { + Self { low: 0, high: 0 } + } + + fn track(&mut self, pulse_msg: &PulseMsg) { + match pulse_msg.pulse { + Pulse::Low => self.low += 1, + Pulse::High => self.high += 1, + } + } +} + +pub fn puzzle_1(input: &str, n_button_presses: u32) -> Result { + let mut modules = parse_input(input)?; + modules + .iter() + .for_each(|(name, module)| log::info!("{} -> {:?}", name, module)); + + let mut pulse_counter = PulseCounter::new(); + for _ in 0..n_button_presses { + let mut pulses = VecDeque::from_iter([PulseMsg { + from: "button".to_string(), + to: "broadcaster".to_string(), + pulse: Pulse::Low, + }]); + pulse_counter.low += 1; + while !pulses.is_empty() { + let pulse = pulses.pop_front().unwrap(); + log::debug!("PULSE: {:?}", pulse); + if let Some(response) = match modules.get_mut(&pulse.to) { + Some(Module::B(b)) => b.receive(&pulse), + Some(Module::C(c)) => c.receive(&pulse), + Some(Module::F(f)) => f.receive(&pulse), + Some(Module::O(o)) => o.receive(&pulse), + None => None, + } { + log::debug!("Received {} responses.", response.len()); + response.into_iter().for_each(|r| { + log::trace!("RESPONSE: {:?}", r); + pulse_counter.track(&r); + pulses.push_back(r); + }); + } else { + log::debug!("No responses.") + } + } + } + log::info!( + "Final counts: {} low, {} high", + pulse_counter.low, + pulse_counter.high + ); + Ok(pulse_counter.low * pulse_counter.high) +} + +pub fn main(data_dir: &str) { + println!("Day 20: Pulse Propagation"); + let data = load(data_dir, 20, None); + + // Puzzle 1. + let _ = env_logger::try_init(); + let answer_1 = puzzle_1(&data, 1000); + match answer_1 { + Ok(x) => println!(" Puzzle 1: {}", x), + Err(e) => panic!("No solution to puzzle 1: {}.", e), + } + assert_eq!(answer_1, Ok(944750144)); + + // Puzzle 2. + // let answer_2 = puzzle_2(&data); + // match answer_2 { + // Ok(x) => println!(" Puzzle 2: {}", x), + // Err(e) => panic!("No solution to puzzle 2: {}", e), + // } + // assert_eq!(answer_2, Ok(30449)) +} diff --git a/src/solutions/mod.rs b/src/solutions/mod.rs index 66f2636..9997b99 100644 --- a/src/solutions/mod.rs +++ b/src/solutions/mod.rs @@ -17,3 +17,4 @@ pub mod day16; pub mod day17; pub mod day18; pub mod day19; +pub mod day20; diff --git a/tests/test_day20.rs b/tests/test_day20.rs new file mode 100644 index 0000000..6da7e20 --- /dev/null +++ b/tests/test_day20.rs @@ -0,0 +1,35 @@ +use aoc_2023::solutions::day20::puzzle_1; + +const EXAMPLE_INPUT_1: &str = " +broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a +"; + +const EXAMPLE_INPUT_2: &str = " +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output +"; + +#[test] +fn puzzle_1_test_1() { + let _ = env_logger::try_init(); + assert_eq!(puzzle_1(self::EXAMPLE_INPUT_1, 1), Ok(32)); +} + +#[test] +fn puzzle_1_test_2() { + let _ = env_logger::try_init(); + assert_eq!(puzzle_1(self::EXAMPLE_INPUT_1, 1000), Ok(32000000)); +} + +#[test] +fn puzzle_1_test_3() { + let _ = env_logger::try_init(); + assert_eq!(puzzle_1(self::EXAMPLE_INPUT_2, 1000), Ok(11687500)); +}