diff --git a/20/Cargo.lock b/20/Cargo.lock index 5ce1436..698d501 100644 --- a/20/Cargo.lock +++ b/20/Cargo.lock @@ -11,11 +11,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "day20" version = "0.1.0" dependencies = [ "lazy-regex", + "num", ] [[package]] @@ -47,6 +54,82 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "once_cell" version = "1.19.0" diff --git a/20/Cargo.toml b/20/Cargo.toml index d705f9d..b05b468 100644 --- a/20/Cargo.toml +++ b/20/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] lazy-regex = "3.1.0" +num = "0.4.1" diff --git a/20/src/main.rs b/20/src/main.rs index 298a158..790327a 100644 --- a/20/src/main.rs +++ b/20/src/main.rs @@ -1,10 +1,12 @@ use std::collections::{BinaryHeap, HashMap, VecDeque}; use std::fmt::Display; use std::fs::File; +use std::hash::{Hash, Hasher}; use std::io::{BufRead, BufReader, Lines}; use std::time::Instant; use lazy_regex::lazy_regex; +use num::integer::lcm; // BOILERPLATE type InputIter = Lines>; @@ -31,7 +33,7 @@ fn main() { const MODULE_PATTERN: lazy_regex::Lazy = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$"); -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] enum PulseType { Low, High, @@ -79,7 +81,15 @@ struct Module { inputs: Vec, outputs: Vec, on: bool, - last_input_states: HashMap, + last_input_states: Vec, +} + +impl Hash for Module { + fn hash(&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 { @@ -90,18 +100,19 @@ impl Module { inputs: Vec::new(), outputs: Vec::new(), 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 { let mut m = Self::new(name); m.kind = Some(kind); m } fn and_destinations(mut self, destinations: &str) -> Self { - println!("{} destinations: {}", self.name, destinations); for dest in destinations.split(", ") { - println!(" {}", dest); self.outputs.push(dest.into()); } self @@ -110,15 +121,29 @@ impl Module { self.add_source(source); self } - fn add_source(&mut self, source: String) { - self.inputs.push(source.clone()); - self.last_input_states.insert(source, PulseType::Low); + fn add_source(&mut self, source: String) { + self.inputs.push(source); + 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 { fn from(s: &str) -> Self { - println!("s: {}", s); let Some(re_result) = MODULE_PATTERN.captures(s) else { panic!("unparseable module: {}", s); }; @@ -166,7 +191,7 @@ struct Machine { modules: HashMap, work_queue: BinaryHeap, count_low: u64, - count_high: u64 + count_high: u64, } impl From> for Machine { @@ -199,7 +224,8 @@ impl From> for Machine { Machine { modules, 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) => { - self.modules - .get_mut(target) - .unwrap() - .last_input_states - .insert(from.to_owned(), signal); - if self.modules[target] - .last_input_states - .values() - .all(|state| *state == PulseType::High) - { + let target_m = self.modules.get_mut(target).unwrap(); + let source_idx = target_m.source_idx(from); + target_m.last_input_states[source_idx] = signal; + if target_m.last_input_states.iter().all(|state| *state == PulseType::High) { self.send_all_outputs(PulseType::Low, target, priority) } else { 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 { + 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 const PROBLEM1_ITERATIONS: usize = 1000; fn problem1(input: Lines) -> u64 { let mut machine = Machine::from(input); - println!("{:?}", machine); for _ in 0..PROBLEM1_ITERATIONS { machine.press_button(); machine.run(); @@ -275,8 +316,31 @@ fn problem1(input: Lines) -> u64 { } // PROBLEM 2 solution -fn problem2(input: Lines) -> u64 { - 0 +fn problem2(input: Lines) -> u128 { + 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)] @@ -295,10 +359,4 @@ mod tests { let c = Cursor::new(EXAMPLE); assert_eq!(problem1(c.lines()), 11687500); } - - #[test] - fn problem2_example() { - let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); - } }