day20: problem 2 solution - needed a hint
This commit is contained in:
		
							
								
								
									
										83
									
								
								20/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										83
									
								
								20/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -11,11 +11,18 @@ dependencies = [ | |||||||
|  "memchr", |  "memchr", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "autocfg" | ||||||
|  | version = "1.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "day20" | name = "day20" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "lazy-regex", |  "lazy-regex", | ||||||
|  |  "num", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -47,6 +54,82 @@ version = "2.6.4" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" | ||||||
|  | dependencies = [ | ||||||
|  |  "num-bigint", | ||||||
|  |  "num-complex", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-iter", | ||||||
|  |  "num-rational", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-bigint" | ||||||
|  | version = "0.4.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-complex" | ||||||
|  | version = "0.4.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" | ||||||
|  | dependencies = [ | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-integer" | ||||||
|  | version = "0.1.45" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-iter" | ||||||
|  | version = "0.1.43" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-rational" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-bigint", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-traits" | ||||||
|  | version = "0.2.17" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "once_cell" | name = "once_cell" | ||||||
| version = "1.19.0" | version = "1.19.0" | ||||||
|   | |||||||
| @@ -7,3 +7,4 @@ edition = "2021" | |||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| lazy-regex = "3.1.0" | lazy-regex = "3.1.0" | ||||||
|  | num = "0.4.1" | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								20/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								20/src/main.rs
									
									
									
									
									
								
							| @@ -1,10 +1,12 @@ | |||||||
| use std::collections::{BinaryHeap, HashMap, VecDeque}; | use std::collections::{BinaryHeap, HashMap, VecDeque}; | ||||||
| use std::fmt::Display; | use std::fmt::Display; | ||||||
| use std::fs::File; | use std::fs::File; | ||||||
|  | use std::hash::{Hash, Hasher}; | ||||||
| use std::io::{BufRead, BufReader, Lines}; | use std::io::{BufRead, BufReader, Lines}; | ||||||
| use std::time::Instant; | use std::time::Instant; | ||||||
|  |  | ||||||
| use lazy_regex::lazy_regex; | use lazy_regex::lazy_regex; | ||||||
|  | use num::integer::lcm; | ||||||
|  |  | ||||||
| // BOILERPLATE | // BOILERPLATE | ||||||
| type InputIter = Lines<BufReader<File>>; | type InputIter = Lines<BufReader<File>>; | ||||||
| @@ -31,7 +33,7 @@ fn main() { | |||||||
|  |  | ||||||
| const MODULE_PATTERN: lazy_regex::Lazy<lazy_regex::Regex> = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$"); | const MODULE_PATTERN: lazy_regex::Lazy<lazy_regex::Regex> = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$"); | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq, Eq, Clone, Copy)] | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] | ||||||
| enum PulseType { | enum PulseType { | ||||||
|     Low, |     Low, | ||||||
|     High, |     High, | ||||||
| @@ -79,7 +81,15 @@ struct Module { | |||||||
|     inputs: Vec<String>, |     inputs: Vec<String>, | ||||||
|     outputs: Vec<String>, |     outputs: Vec<String>, | ||||||
|     on: bool, |     on: bool, | ||||||
|     last_input_states: HashMap<String, PulseType>, |     last_input_states: Vec<PulseType>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Hash for Module { | ||||||
|  |     fn hash<H: Hasher>(&self, state: &mut H) { | ||||||
|  |         // the only states that change are 'on' and 'last_input_states', the rest are constant once created | ||||||
|  |         self.on.hash(state); | ||||||
|  |         self.last_input_states.hash(state); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Module { | impl Module { | ||||||
| @@ -90,18 +100,19 @@ impl Module { | |||||||
|             inputs: Vec::new(), |             inputs: Vec::new(), | ||||||
|             outputs: Vec::new(), |             outputs: Vec::new(), | ||||||
|             on: false, |             on: false, | ||||||
|             last_input_states: HashMap::new(), |             last_input_states: Vec::new(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     fn source_idx(&self, name: &str) -> usize { | ||||||
|  |         self.inputs.iter().position(|in_name| in_name == name).unwrap() | ||||||
|  |     } | ||||||
|     fn with_kind(name: String, kind: ModuleKind) -> Self { |     fn with_kind(name: String, kind: ModuleKind) -> Self { | ||||||
|         let mut m = Self::new(name); |         let mut m = Self::new(name); | ||||||
|         m.kind = Some(kind); |         m.kind = Some(kind); | ||||||
|         m |         m | ||||||
|     } |     } | ||||||
|     fn and_destinations(mut self, destinations: &str) -> Self { |     fn and_destinations(mut self, destinations: &str) -> Self { | ||||||
|         println!("{} destinations: {}", self.name, destinations); |  | ||||||
|         for dest in destinations.split(", ") { |         for dest in destinations.split(", ") { | ||||||
|             println!("  {}", dest); |  | ||||||
|             self.outputs.push(dest.into()); |             self.outputs.push(dest.into()); | ||||||
|         } |         } | ||||||
|         self |         self | ||||||
| @@ -111,14 +122,28 @@ impl Module { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|     fn add_source(&mut self, source: String) {     |     fn add_source(&mut self, source: String) {     | ||||||
|         self.inputs.push(source.clone()); |         self.inputs.push(source); | ||||||
|         self.last_input_states.insert(source, PulseType::Low); |         self.last_input_states.push(PulseType::Low); | ||||||
|  |     } | ||||||
|  |     fn reset(&mut self) { | ||||||
|  |         // relevant state is on and last_input_states | ||||||
|  |         self.on = false; | ||||||
|  |         for input in &mut self.last_input_states { | ||||||
|  |             *input = PulseType::Low; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fn state_space(&self) -> u128 { | ||||||
|  |         match self.kind { | ||||||
|  |             Some(ModuleKind::Broadcast) | Some(ModuleKind::Button) => 1, | ||||||
|  |             Some(ModuleKind::FlipFlop) => 2, | ||||||
|  |             Some(ModuleKind::Conjunction) => 1 << self.last_input_states.len(), // 2^n | ||||||
|  |             None => 1 | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<&str> for Module { | impl From<&str> for Module { | ||||||
|     fn from(s: &str) -> Self { |     fn from(s: &str) -> Self { | ||||||
|         println!("s: {}", s); |  | ||||||
|         let Some(re_result) = MODULE_PATTERN.captures(s) else { |         let Some(re_result) = MODULE_PATTERN.captures(s) else { | ||||||
|             panic!("unparseable module: {}", s); |             panic!("unparseable module: {}", s); | ||||||
|         }; |         }; | ||||||
| @@ -166,7 +191,7 @@ struct Machine { | |||||||
|     modules: HashMap<String, Module>, |     modules: HashMap<String, Module>, | ||||||
|     work_queue: BinaryHeap<Job>, |     work_queue: BinaryHeap<Job>, | ||||||
|     count_low: u64, |     count_low: u64, | ||||||
|     count_high: u64 |     count_high: u64, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<T: BufRead> From<Lines<T>> for Machine { | impl<T: BufRead> From<Lines<T>> for Machine { | ||||||
| @@ -199,7 +224,8 @@ impl<T: BufRead> From<Lines<T>> for Machine { | |||||||
|         Machine { |         Machine { | ||||||
|             modules, |             modules, | ||||||
|             work_queue: BinaryHeap::new(), |             work_queue: BinaryHeap::new(), | ||||||
|             count_low: 0, count_high: 0 |             count_low: 0, | ||||||
|  |             count_high: 0, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -227,16 +253,10 @@ impl Machine { | |||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             Some(ModuleKind::Conjunction) => { |             Some(ModuleKind::Conjunction) => { | ||||||
|                 self.modules |                 let target_m = self.modules.get_mut(target).unwrap(); | ||||||
|                     .get_mut(target) |                 let source_idx = target_m.source_idx(from); | ||||||
|                     .unwrap() |                 target_m.last_input_states[source_idx] = signal; | ||||||
|                     .last_input_states |                 if target_m.last_input_states.iter().all(|state| *state == PulseType::High) { | ||||||
|                     .insert(from.to_owned(), signal); |  | ||||||
|                 if self.modules[target] |  | ||||||
|                     .last_input_states |  | ||||||
|                     .values() |  | ||||||
|                     .all(|state| *state == PulseType::High) |  | ||||||
|                 { |  | ||||||
|                     self.send_all_outputs(PulseType::Low, target, priority) |                     self.send_all_outputs(PulseType::Low, target, priority) | ||||||
|                 } else { |                 } else { | ||||||
|                     self.send_all_outputs(PulseType::High, target, priority) |                     self.send_all_outputs(PulseType::High, target, priority) | ||||||
| @@ -260,13 +280,34 @@ impl Machine { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     // the number of cycles it took until the target sends a low pulse, or None if the target node sent no low pulses but the job finished | ||||||
|  |     fn time_to_state(&mut self, goal:&str, state: PulseType) -> Option<u64> { | ||||||
|  |         while let Some(job) = self.work_queue.pop() { | ||||||
|  |             if job.from == goal && job.signal == state { | ||||||
|  |                 return Some(self.count_high + self.count_low); | ||||||
|  |             } | ||||||
|  |             for target in job.targets { | ||||||
|  |                 self.send_pulse(job.signal, &target, &job.from, job.priority); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  |     fn reset(&mut self) { | ||||||
|  |         for (_name, m) in &mut self.modules { | ||||||
|  |             m.reset(); | ||||||
|  |         } | ||||||
|  |         self.count_low = 0; | ||||||
|  |         self.count_high = 0; | ||||||
|  |     } | ||||||
|  |     fn state_space(&self) -> u128 { | ||||||
|  |         self.modules.values().map(|m| m.state_space()).product() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // PROBLEM 1 solution | // PROBLEM 1 solution | ||||||
| const PROBLEM1_ITERATIONS: usize = 1000; | const PROBLEM1_ITERATIONS: usize = 1000; | ||||||
| fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | ||||||
|     let mut machine = Machine::from(input); |     let mut machine = Machine::from(input); | ||||||
|     println!("{:?}", machine); |  | ||||||
|     for _ in 0..PROBLEM1_ITERATIONS { |     for _ in 0..PROBLEM1_ITERATIONS { | ||||||
|         machine.press_button(); |         machine.press_button(); | ||||||
|         machine.run(); |         machine.run(); | ||||||
| @@ -275,8 +316,31 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 { | |||||||
| } | } | ||||||
|  |  | ||||||
| // PROBLEM 2 solution | // PROBLEM 2 solution | ||||||
| fn problem2<T: BufRead>(input: Lines<T>) -> u64 { | fn problem2<T: BufRead>(input: Lines<T>) -> u128 { | ||||||
|     0 |     let mut machine = Machine::from(input); | ||||||
|  |     println!("STATES: {}", machine.state_space()); | ||||||
|  |     // Find the rx module and look at its parent(s) | ||||||
|  |     let rx = &machine.modules["rx"]; | ||||||
|  |     // my input has a single conjunction as parent, todo implement other possibilities | ||||||
|  |     let con = &machine.modules[&rx.inputs[0]]; | ||||||
|  |  | ||||||
|  |     // for each input, find how long it takes for it to be High, so the conjunction sends a low to rx | ||||||
|  |     let mut cycles: Vec<_> = Vec::new(); | ||||||
|  |     for input in con.inputs.clone() { | ||||||
|  |         print!("searching distance to {}...", input); | ||||||
|  |         machine.reset(); | ||||||
|  |         let mut button_count = 0; | ||||||
|  |         loop { | ||||||
|  |             machine.press_button(); | ||||||
|  |             button_count += 1; | ||||||
|  |             if let Some(distance) = machine.time_to_state(&input, PulseType::High) { | ||||||
|  |                 println!("got {} pulses, {} button presses", distance, button_count); | ||||||
|  |                 cycles.push(button_count as u128); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     cycles.iter().fold(1, |accum, cycle| lcm(accum, *cycle)) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @@ -295,10 +359,4 @@ mod tests { | |||||||
|         let c = Cursor::new(EXAMPLE); |         let c = Cursor::new(EXAMPLE); | ||||||
|         assert_eq!(problem1(c.lines()), 11687500); |         assert_eq!(problem1(c.lines()), 11687500); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn problem2_example() { |  | ||||||
|         let c = Cursor::new(EXAMPLE); |  | ||||||
|         assert_eq!(problem2(c.lines()), 0); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user