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)]