use std::collections::VecDeque; #[path = "../lib.rs"] mod lib; const LOW: bool = false; const HIGH: bool = true; #[derive(Clone, Copy, Debug)] enum Module { Broadcaster, FlipFlop, Conjunction, } fn parse_input() -> Vec<(String, Module, Vec)> { let mut modules = vec![]; let mut names = vec![ "broadcaster".to_string(), "sink".to_string(), "hn".to_string(), "fz".to_string(), "xf".to_string(), "mp".to_string(), "xn".to_string(), ]; for _ in names.iter() { modules.push((Module::Broadcaster, vec![])); } for line in lib::iter_input() { let (head, tail) = line.split_once(" -> ").unwrap(); let (name, module) = match head.bytes().next().unwrap() { b'b' => (head.to_string(), Module::Broadcaster), b'%' => (head[1..].to_string(), Module::FlipFlop), b'&' => (head[1..].to_string(), Module::Conjunction), _ => unreachable!(), }; let target_names = tail.split(", ").map(|s| s.to_string()).collect(); if let Some(i) = names.iter().position(|n| *n == name) { modules[i] = (module, target_names); } else { names.push(name); modules.push((module, target_names)); } } return modules .iter() .enumerate() .map(|(i, (module, target_names))| { let targets = target_names .iter() .map(|name| match names.iter().position(|n| n == name) { Some(i) => i, None => 1, }) .collect(); return (names[i].clone(), *module, targets); }) .collect(); } fn push_the_button( modules: &Vec<(String, Module, Vec)>, state: &mut Vec<(bool, bool)>, ) -> (usize, usize, [bool; 4]) { let mut queue = VecDeque::new(); let mut lows = 0; let mut highs = 0; let mut part2 = [false; 4]; queue.push_back((0, LOW)); while let Some((cur, pulse)) = queue.pop_front() { if pulse == HIGH { highs += 1; } else { lows += 1; } match &modules[cur] { (_name, Module::Broadcaster, targets) => { state[cur].1 = pulse; for next in targets.iter() { queue.push_back((*next, pulse)); } } (_name, Module::FlipFlop, targets) => { if pulse == LOW { let p; if state[cur].0 { state[cur].0 = false; p = LOW; } else { state[cur].0 = true; p = HIGH; } state[cur].1 = p; for next in targets.iter() { queue.push_back((*next, p)); } } } (_name, Module::Conjunction, targets) => { let all = (0..modules.len()).all(|i| state[i].1 == HIGH || !modules[i].2.contains(&cur)); let p = if all { LOW } else { HIGH }; state[cur].1 = p; match (cur, p) { (2, true) => part2[0] = true, (3, true) => part2[1] = true, (4, true) => part2[2] = true, (5, true) => part2[3] = true, _ => {} }; for next in targets.iter() { queue.push_back((*next, p)); } } } } return (highs, lows, part2); } fn gcd(a: usize, b: usize) -> usize { let mut x = a.max(b); let mut y = a.min(b); while y > 0 { (x, y) = (y, x % y); } return x; } fn lcm(a: usize, b: usize) -> usize { return a * b / gcd(a, b); } fn main() { let modules = parse_input(); let mut state = vec![(false, LOW); modules.len()]; let mut highs = 0; let mut lows = 0; let mut part2 = [0; 4]; for i in 1.. { let (h, l, p2) = push_the_button(&modules, &mut state); if i <= 1000 { highs += h; lows += l; } for j in 0..4 { if part2[j] == 0 && p2[j] { part2[j] = i; } } if i > 1000 && part2.iter().all(|&x| x != 0) { break; } } println!("part1: {}", lows * highs); // part 2 is a bit shaky. I looked at the graph of nodes and // realized that there are 4 subgraphs that each feed into &xn. // I was not sure if they reset to the initial state after that, // but simply calculating their lcm gave the correct answer. println!( "part2: {}", lcm(lcm(part2[0], part2[1]), lcm(part2[2], part2[3])) ); }