diff --git a/samples/day20.txt b/samples/day20.txt new file mode 100644 index 0000000..4da4379 --- /dev/null +++ b/samples/day20.txt @@ -0,0 +1,5 @@ +broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output \ No newline at end of file diff --git a/src/bin/day20.rs b/src/bin/day20.rs new file mode 100644 index 0000000..906f2ee --- /dev/null +++ b/src/bin/day20.rs @@ -0,0 +1,227 @@ +use std::{ + collections::{HashMap, HashSet, VecDeque}, + hash::{DefaultHasher, Hash, Hasher}, + str::FromStr, +}; + +use aoc_2023::*; + +type Output1 = usize; +type Output2 = Output1; + +lazy_static! { + static ref GATE_REGEX: Regex = Regex::new(r"([%&]?)(.*) -> (.*)").unwrap(); +} + +fn gate_hash(name: &str) -> u64 { + let mut h = DefaultHasher::new(); + name.hash(&mut h); + h.finish() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum GateState { + Repeater, + FlipFlop(bool), + Nand(u32), +} + +impl FromStr for GateState { + type Err = &'static str; + + fn from_str(s: &str) -> std::result::Result { + match s { + "" => Ok(GateState::Repeater), + "%" => Ok(GateState::FlipFlop(false)), + "&" => Ok(GateState::Nand(0)), + _ => Err("Invalid kind"), + } + } +} + +impl GateState { + fn exec( + &mut self, + watch: Option, + src: u64, + dest: &Gate, + p: bool, + ) -> Option<(Option, bool)> { + match self { + GateState::Repeater => Some((None, p)), + GateState::FlipFlop(b) => { + if p { + None + } else { + *b = !*b; + Some((None, *b)) + } + } + GateState::Nand(v) => { + let idx = dest + .inputs + .iter() + .position(|i| gate_hash(i) == src) + .unwrap(); + + let mut high = None; + if p { + if watch.is_some_and(|h| dest.hash == h) { + high = Some(idx); + } + + *v |= 1 << idx; + } else { + *v &= !(1 << idx); + } + + Some((high, *v != (1 << dest.inputs.len()) - 1)) + } + } + } +} + +#[derive(Debug, Clone, Default)] +struct Gate { + inputs: Vec, + outputs: Vec, + hash: u64, +} + +impl Gate { + fn new(name: &str) -> Gate { + Gate { + inputs: vec![], + outputs: vec![], + hash: gate_hash(name), + } + } +} + +struct Day20 { + gates: HashMap, + initial_state: HashMap, +} + +impl Day20 { + fn push( + &self, + state: &mut HashMap, + watch: Option, + ) -> (usize, usize, Option) { + let mut q = VecDeque::new(); + let mut counters = vec![0, 0]; + let mut high_idx = None; + + q.push_back(("button".to_owned(), "broadcaster".to_owned(), false)); + while let Some((src, dest, pulse)) = q.pop_front() { + counters[pulse as usize] += 1; + + let (Some(g), Some(gs)) = (self.gates.get(&dest), state.get_mut(&dest)) else { + continue; + }; + + if let Some((found_idx, next_pulse)) = gs.exec(watch, gate_hash(&src), g, pulse) { + if found_idx.is_some() { + high_idx = found_idx; + } + + for next_gate in &g.outputs { + q.push_back((dest.clone(), next_gate.clone(), next_pulse)); + } + } + } + + (counters[0], counters[1], high_idx) + } +} + +impl Solution for Day20 { + fn new>(pathname: P) -> Self { + let lines: Vec = file_to_lines(pathname); + + let mut gates: HashMap = HashMap::new(); + let mut initial_state: HashMap = HashMap::new(); + + for (kind, name, outputs) in lines.into_iter().map(|g| { + let (_, [kind, name, outputs]) = GATE_REGEX.captures(&g).unwrap().extract(); + ( + kind.to_owned(), + name.to_owned(), + outputs.split(", ").map(str::to_owned).collect_vec(), + ) + }) { + for o in &outputs { + gates + .entry(o.clone()) + .or_default() + .inputs + .push(name.clone()); + } + + gates + .entry(name.clone()) + .or_insert(Gate::new(&name)) + .outputs = outputs; + initial_state.insert(name, kind.parse().unwrap()); + } + + assert_eq!( + gates + .keys() + .map(|k| gate_hash(k)) + .collect::>() + .len(), + gates.len(), + "no collisions in hash" + ); + + Self { + gates, + initial_state, + } + } + + fn part_1(&mut self) -> Output1 { + let mut state = self.initial_state.clone(); + let (mut l, mut h) = (0, 0); + + for _ in 0..1000 { + let (l_iter, h_iter, _) = self.push(&mut state, None); + l += l_iter; + h += h_iter; + } + + l * h + } + + fn part_2(&mut self) -> Output2 { + let rx_parent = &self.gates["rx"].inputs[0]; + let rx_parent_hash = Some(gate_hash(&rx_parent)); + + let mut cycles = vec![0_usize; self.gates[rx_parent].inputs.len()]; + let mut found = 0; + + let mut state = self.initial_state.clone(); + for i in 1.. { + if found >= cycles.len() { + break; + } + + if let (_, _, Some(idx)) = self.push(&mut state, rx_parent_hash) { + if cycles[idx] == 0 { + cycles[idx] = i; + found += 1; + } + } + } + + cycles.into_iter().product1().unwrap() + } +} + +fn main() -> Result<()> { + Day20::main() +} + +test_sample!(day_20, Day20, 11687500, 0);