diff --git a/rust/2023/README.md b/rust/2023/README.md index 943e176..5c79087 100644 --- a/rust/2023/README.md +++ b/rust/2023/README.md @@ -35,6 +35,8 @@ You try to ask why they can't just use a weather machine ("not powerful enough") | [Day 17: Clumsy Crucible](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_17.rs) | ๐ŸŒŸ | 1013 | ๐ŸŒŸ | 1215 | | [Day 18: Lavaduct Lagoon](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_18.rs) | ๐ŸŒŸ | 48652 | ๐ŸŒŸ | 45757884535661 | | [Day 19: Aplenty](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_19.rs) | ๐ŸŒŸ | 331208 | ๐ŸŒŸ | 121464316215623 | +| [Day 20: Pulse Propagation](https://github.com/believer/advent-of-code/blob/master/rust/2023/src/day_19.rs) | ๐ŸŒŸ | 812609846 | ๐ŸŒŸ | 245114020323037 | +| | ## Performance @@ -61,6 +63,7 @@ With the help of [cargo-aoc](https://github.com/gobanos/cargo-aoc) I get automat | 17 | 64.21 ms | 173.37 ms | | 47.36 ยตs | | 18 | 837.59 ns | 844.43 ns | | 41.30 ยตs / 73.29 ยตs | | 19 | 55.27 ยตs | 160.90 ยตs | | 213.26 ยตs | +| 20 | 5.22 ms | 24.31 ms | | 24.13 ยตs | \* compared to first solution
\*\* slow, didn't benchmark. Value comes from running the solver. diff --git a/rust/2023/src/day_20.rs b/rust/2023/src/day_20.rs index c3d938f..14e09f8 100644 --- a/rust/2023/src/day_20.rs +++ b/rust/2023/src/day_20.rs @@ -1,7 +1,15 @@ -//! Day 20 +//! Day 20: Pulse Propagation +//! +//! Hard to follow and my data structures make it even harder haha. Seems +//! like a bunch of people had problems with this one. Might come back and +//! simplify it later. Again HyperNeutrino's solution made it clearer to +//! understand what should be done, but it seems like most people need +//! to make some assumptions about the input to get it to work. use std::collections::{HashMap, VecDeque}; +use crate::math; + #[derive(Debug)] pub struct Input { modules: HashMap, @@ -105,7 +113,7 @@ pub fn input_generator(input: &str) -> Input { #[doc = r#"``` use advent_of_code_2023::day_20::*; let data = include_str!("../input/2023/day20.txt"); -assert_eq!(solve_part_01(&input_generator(data)), 331208); +assert_eq!(solve_part_01(&input_generator(data)), 812609846); ```"#] #[aoc(day20, part1)] pub fn solve_part_01(input: &Input) -> u64 { @@ -184,16 +192,129 @@ pub fn solve_part_01(input: &Input) -> u64 { /* Part Two * +* Some assumptions are made about the input to get this to work. We find +* that the module that feeds into "rx" is "hp" and that "hp" is fed by +* four other modules. "hp" is a conjuction module which means that it will +* only send a low pulse if all of its inputs are high. So, we can +* find the cycle length of each of the modules that feed into "hp" and +* then calculate the LCM of those cycle lengths. * */ #[doc = r#"``` use advent_of_code_2023::day_20::*; let data = include_str!("../input/2023/day20.txt"); -assert_eq!(solve_part_02(&input_generator(data)), 121464316215623); +assert_eq!(solve_part_02(&input_generator(data)), 245114020323037); ```"#] #[aoc(day20, part2)] -pub fn solve_part_02(_input: &Input) -> u64 { - todo!() +pub fn solve_part_02(input: &Input) -> u64 { + let mut modules = input.modules.clone(); + let mut button_presses = 0; + + // Find which module feeds into "rx". This should only be one. + // This is "hp" in my input. + let feed = input + .modules + .values() + .find(|module| module.outputs.contains(&"rx".to_string())) + .unwrap(); + + // Find the modules that feed into the module, "hp", that feeds into "rx". + // We'll use these to find the cycle length and then we can calculate the + // LCM of the cycle lengths. + let mut cycle_lengths: HashMap = HashMap::new(); + + let mut seen = input + .modules + .values() + .filter(|module| module.outputs.contains(&feed.name)) + .map(|module| (module.name.clone(), 0)) + .collect::>(); + + let fewest_button_presses = + 'outer: loop { + button_presses += 1; + + let mut queue = VecDeque::new(); + + for target in input.broadcast_target.iter() { + queue.push_back(("broadcaster".to_string(), target.clone(), Memory::Low)); + } + + while let Some((from, to, pulse)) = queue.pop_front() { + // Handles unknown modules + if !modules.contains_key(&to) { + continue; + }; + + let module = modules.get_mut(&to).unwrap(); + + // We only care about the module that feeds into "hp" + // and we only care about high pulses + if module.name == feed.name && pulse == Memory::High { + seen.entry(from.clone()).and_modify(|x| *x += 1); + + // Update the cycle length + if !cycle_lengths.contains_key(&from) { + cycle_lengths.insert(from.clone(), button_presses); + } + + // We've seen all the modules that feed into "hp" + // Calculate the LCM of the cycle lengths and break + if seen.values().all(|x| *x == 1) { + break 'outer cycle_lengths + .values() + .fold(1, |acc, x| math::lcm(acc, *x as i64)); + } + } + + match module.module_type { + ModuleType::FlipFlop => { + if pulse == Memory::Low { + module.memory = if module.memory == Memory::Off { + Memory::On + } else { + Memory::Off + }; + let next_pulse = if module.memory == Memory::On { + Memory::High + } else { + Memory::Low + }; + + for output in module.outputs.iter() { + queue.push_back(( + module.name.clone(), + output.clone(), + next_pulse.clone(), + )); + } + } + } + + ModuleType::Conjuction => { + if let Memory::Map(ref mut map) = module.memory { + map.insert(from, pulse.clone()); + + let next_pulse = if map.values().all(|x| *x == Memory::High) { + Memory::Low + } else { + Memory::High + }; + + for output in module.outputs.iter() { + queue.push_back(( + module.name.clone(), + output.clone(), + next_pulse.clone(), + )); + } + } + } + } + } + }; + + fewest_button_presses as u64 } #[cfg(test)]