From 4ff47dfc8b473827b6ddec781e92f27fa2a461f4 Mon Sep 17 00:00:00 2001 From: Anthony Oteri Date: Wed, 20 Dec 2023 18:27:12 -0500 Subject: [PATCH] Day 20 - Part 2 Signed-off-by: Anthony Oteri --- day-20/src/part2.rs | 282 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 275 insertions(+), 7 deletions(-) diff --git a/day-20/src/part2.rs b/day-20/src/part2.rs index 1ff3976..5d6ebd6 100644 --- a/day-20/src/part2.rs +++ b/day-20/src/part2.rs @@ -1,18 +1,286 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{alpha1, line_ending}, + multi::separated_list1, + sequence::preceded, + IResult, +}; + use crate::error::AocError; -#[tracing::instrument] -pub fn process(input: &str) -> miette::Result { - Ok(0) +#[derive(Debug)] +enum State { + On, + Off, } +#[derive(Debug, Copy, Clone)] +enum Signal { + High, + Low, +} + +#[derive(Debug)] +enum ModuleType<'a> { + Broadcaster, + FlipFlop { state: State }, + Conjunction { inputs: HashMap<&'a str, Signal> }, + Test, +} + +#[derive(Debug)] +struct Module<'a> { + id: &'a str, + module_type: ModuleType<'a>, + outputs: Vec<&'a str>, +} + +impl<'a> Module<'a> { + #[tracing::instrument] + fn process(&mut self, from: &'a str, signal: &Signal) -> Vec<(&'a str, &'a str, Signal)> { + match (&mut self.module_type, signal) { + (ModuleType::Broadcaster, signal) => self + .outputs + .iter() + .map(|o| (self.id, *o, *signal)) + .collect(), + (ModuleType::FlipFlop { .. }, Signal::High) => { + vec![] + } + (ModuleType::FlipFlop { state: State::On }, Signal::Low) => { + self.module_type = ModuleType::FlipFlop { state: State::Off }; + self.outputs + .iter() + .map(|o| (self.id, *o, Signal::Low)) + .collect() + } + (ModuleType::FlipFlop { state: State::Off }, Signal::Low) => { + self.module_type = ModuleType::FlipFlop { state: State::On }; + self.outputs + .iter() + .map(|o| (self.id, *o, Signal::High)) + .collect() + } + (ModuleType::Conjunction { ref mut inputs }, pulse) => { + inputs.entry(from).and_modify(|s| *s = *pulse); + if inputs.values().all(|s| matches!(s, Signal::High)) { + self.outputs + .iter() + .map(|o| (self.id, *o, Signal::Low)) + .collect() + } else { + self.outputs + .iter() + .map(|o| (self.id, *o, Signal::High)) + .collect() + } + } + _ => { + vec![] + } + } + } +} + +fn broadcaster(input: &str) -> IResult<&str, Module> { + let (input, id) = alpha1(input)?; + let (input, _) = tag(" -> ")(input)?; + let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?; + + Ok(( + input, + Module { + id, + module_type: ModuleType::Broadcaster, + outputs, + }, + )) +} + +fn flip_flop(input: &str) -> IResult<&str, Module> { + let (input, id) = preceded(nom::character::complete::char('%'), alpha1)(input)?; + let (input, _) = tag(" -> ")(input)?; + let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?; + + Ok(( + input, + Module { + id, + module_type: ModuleType::FlipFlop { state: State::Off }, + outputs, + }, + )) +} + +fn conjunction(input: &str) -> IResult<&str, Module> { + let (input, id) = preceded(nom::character::complete::char('&'), alpha1)(input)?; + let (input, _) = tag(" -> ")(input)?; + let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?; + + Ok(( + input, + Module { + id, + module_type: ModuleType::Conjunction { + inputs: HashMap::new(), + }, + outputs, + }, + )) +} + +fn parser(input: &str) -> IResult<&str, HashMap<&str, Module>> { + let (input, modules) = + separated_list1(line_ending, alt((broadcaster, flip_flop, conjunction)))(input)?; + + let mut module_map = modules + .into_iter() + .map(|m| (m.id, m)) + .collect::>(); + + let conjunctions: Vec<&str> = module_map + .iter() + .filter(|(_, m)| matches!(m.module_type, ModuleType::Conjunction { .. })) + .map(|(id, _)| *id) + .collect(); + + let all_inputs: HashSet<&str> = module_map.keys().copied().collect(); + let all_outputs: HashSet<&str> = module_map + .iter() + .flat_map(|(_, m)| m.outputs.iter().copied()) + .collect(); + for module in all_outputs.difference(&all_inputs) { + module_map.entry(module).or_insert(Module { + id: module, + module_type: ModuleType::Test, + outputs: Vec::default(), + }); + } + + for conjunction in conjunctions { + let inputs: Vec<&str> = module_map + .iter() + .filter(|(_, m)| m.outputs.contains(&conjunction)) + .map(|(id, _)| *id) + .collect(); + + for input in inputs { + module_map.entry(conjunction).and_modify(|m| { + if let ModuleType::Conjunction { inputs, .. } = &mut m.module_type { + inputs.insert(input, Signal::Low); + } + }); + } + } + + Ok((input, module_map)) +} + +#[tracing::instrument(skip(input))] +pub fn process(input: &str) -> miette::Result { + let (input, mut modules) = parser(input).unwrap(); + debug_assert!(input.is_empty()); + + let target = modules + .iter() + .filter_map(|(id, m)| match m.module_type { + ModuleType::Test => Some(*id), + _ => None, + }) + .next() + .unwrap(); + + let target_parents = modules + .iter() + .filter_map(|(id, m)| { + if m.outputs.contains(&target) { + Some(*id) + } else { + None + } + }) + .collect::>(); + + let mut target_grandparents = target_parents + .iter() + .flat_map(|p| { + modules + .iter() + .filter_map(|(gp, m)| { + if m.outputs.contains(p) { + Some(*gp) + } else { + None + } + }) + .collect::>() + }) + .collect::>(); + + let mut loops: HashMap<&str, usize> = HashMap::default(); + + for i in 1.. { + let mut queue = VecDeque::new(); + queue.push_back(("button", "broadcaster", Signal::Low)); + + loop { + if queue.is_empty() { + break; + } + let (from, to, signal) = queue.pop_front().unwrap(); + if target_grandparents.contains(&to) && matches!(signal, Signal::Low) { + loops.insert(to, i); + target_grandparents.retain(|gp| *gp != to); + } + + tracing::info!("{} -{:?}-> {}", from, signal, to); + let module = modules.get_mut(to).unwrap(); + let signals = module.process(from, &signal); + queue.extend(signals); + } + + if target_grandparents.is_empty() { + break; + } + } + + dbg!(&loops); + + Ok(lcm(&loops.values().copied().collect::>()) as u64) +} + +fn lcm(nums: &[usize]) -> usize { + if nums.len() == 1 { + return nums[0]; + } + let a = nums[0]; + let b = lcm(&nums[1..]); + a * b / gcd(a, b) +} + +fn gcd(a: usize, b: usize) -> usize { + if b == 0 { + return a; + } + gcd(b, a % b) +} #[cfg(test)] mod tests { use super::*; - #[test_log::test] - fn test_process() -> miette::Result<()> { - let input = include_str!("../test-input.txt"); - assert_eq!(0, process(input)?); + use rstest::rstest; + + #[test_log::test(rstest)] + #[case("test-input2.txt", 11687500)] + fn test_process(#[case] filename: &str, #[case] expected: u64) -> miette::Result<()> { + let input = + String::from_utf8_lossy(&std::fs::read(std::path::Path::new(filename)).unwrap()) + .parse::() + .unwrap(); + assert_eq!(expected, process(&input)?); Ok(()) } }