diff --git a/20/Cargo.lock b/20/Cargo.lock new file mode 100644 index 0000000..5ce1436 --- /dev/null +++ b/20/Cargo.lock @@ -0,0 +1,118 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "day20" +version = "0.1.0" +dependencies = [ + "lazy-regex", +] + +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/20/Cargo.toml b/20/Cargo.toml new file mode 100644 index 0000000..d705f9d --- /dev/null +++ b/20/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day20" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy-regex = "3.1.0" diff --git a/20/input b/20/input new file mode 100644 index 0000000..71d3d84 --- /dev/null +++ b/20/input @@ -0,0 +1,58 @@ +%hm -> cr +%qc -> nd +&dh -> rm +%ph -> zz +%ps -> kc, dt +%qb -> dt +%jl -> vt, tb +%fh -> dm, gr +broadcaster -> np, mg, vd, xr +%zz -> sq +&rm -> rx +%nd -> br +%nx -> vr, vt +%qf -> dt, dv +%np -> xm, ph +%dm -> nf, gr +%sq -> kj +%bv -> fp, xm +%br -> kt +%mg -> dz, gr +&dt -> vd, dv, dh, hm, ks, hd, kq +%ks -> qf +&qd -> rm +%xr -> vt, rn +%vr -> tg, vt +%lc -> xm +%tq -> gr, fh +%cr -> kq, dt +%vd -> dt, ks +%tb -> nx +%dz -> gr, fd +&gr -> dp, mg, fd, qn +%nf -> gr +%dv -> hm +%qj -> lc, xm +%kc -> dt, gf +%gf -> dt, qb +%vh -> xm, sv +%sr -> vt +%fp -> qg, xm +%kj -> vh +%pc -> tq, gr +%kq -> hd +%xd -> xg, gr +%tg -> sr, vt +&bb -> rm +%rn -> vt, qc +%hd -> ps +%qg -> xm, qj +&dp -> rm +%qn -> pc +%kt -> jl +%sv -> bv +&vt -> bb, nd, qc, xr, br, tb, kt +%fd -> mx +&xm -> zz, sv, sq, ph, kj, np, qd +%xg -> gr, qn +%mx -> gr, xd diff --git a/20/src/main.rs b/20/src/main.rs new file mode 100644 index 0000000..298a158 --- /dev/null +++ b/20/src/main.rs @@ -0,0 +1,304 @@ +use std::collections::{BinaryHeap, HashMap, VecDeque}; +use std::fmt::Display; +use std::fs::File; +use std::io::{BufRead, BufReader, Lines}; +use std::time::Instant; + +use lazy_regex::lazy_regex; + +// BOILERPLATE +type InputIter = Lines>; + +fn get_input() -> InputIter { + let f = File::open("input").unwrap(); + let br = BufReader::new(f); + br.lines() +} + +fn main() { + let start = Instant::now(); + let ans1 = problem1(get_input()); + let duration = start.elapsed(); + println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); + + let start = Instant::now(); + let ans2 = problem2(get_input()); + let duration = start.elapsed(); + println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); +} + +// PARSE + +const MODULE_PATTERN: lazy_regex::Lazy = lazy_regex!("^([%&]?)([a-z]+) -> ([a-z, ]+)$"); + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum PulseType { + Low, + High, +} + +impl From for PulseType { + fn from(value: bool) -> Self { + match value { + true => PulseType::High, + false => PulseType::Low, + } + } +} + +impl From for bool { + fn from(value: PulseType) -> Self { + match value { + PulseType::High => true, + PulseType::Low => false, + } + } +} + +impl Display for PulseType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PulseType::Low => f.write_str("low"), + PulseType::High => f.write_str("high"), + } + } +} + +#[derive(Debug, Clone, Copy)] +enum ModuleKind { + FlipFlop, + Conjunction, + Broadcast, + Button, +} + +#[derive(Debug, Clone)] +struct Module { + name: String, + kind: Option, + inputs: Vec, + outputs: Vec, + on: bool, + last_input_states: HashMap, +} + +impl Module { + fn new(name: String) -> Self { + Self { + name, + kind: None, + inputs: Vec::new(), + outputs: Vec::new(), + on: false, + last_input_states: HashMap::new(), + } + } + 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 + } + fn and_source(mut self, source: String) -> Self { + 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); + } +} + +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); + }; + let (_, [kind, name, destinations]) = re_result.extract(); + if name == "broadcaster" { + return Module::with_kind(name.into(), ModuleKind::Broadcast).and_destinations(destinations); + }; + match kind { + "%" => Module::with_kind(name.into(), ModuleKind::FlipFlop).and_destinations(destinations), + "&" => Module::with_kind(name.into(), ModuleKind::Conjunction).and_destinations(destinations), + _ => panic!("invalid module kind {}", kind), + } + } +} + +#[derive(Debug)] +struct Job { + signal: PulseType, + targets: Vec, + from: String, + priority: usize, +} + +impl PartialEq for Job { + fn eq(&self, other: &Self) -> bool { + self.priority == other.priority + } +} +impl Eq for Job {} + +impl PartialOrd for Job { + fn partial_cmp(&self, other: &Self) -> Option { + other.priority.partial_cmp(&self.priority) + } +} + +impl Ord for Job { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + +#[derive(Debug)] +struct Machine { + modules: HashMap, + work_queue: BinaryHeap, + count_low: u64, + count_high: u64 +} + +impl From> for Machine { + fn from(mut lines: Lines) -> Self { + let mut modules = HashMap::from([( + "button".into(), + Module::with_kind("button".into(), ModuleKind::Button).and_destinations("broadcaster"), + )]); + while let Some(Ok(line)) = lines.next() { + let mut module = Module::from(line.as_str()); + for output in &module.outputs { + if let Some(output_mod) = modules.get_mut(output) { + // already exists, push to input list + output_mod.add_source(module.name.to_owned()); + } else { + // forward declaration of 'unknown' modules + modules.insert( + output.clone(), + Module::new(output.clone()).and_source(module.name.clone()), + ); + } + } + if let Some(existing_mod) = modules.get(&module.name) { + module.inputs = existing_mod.inputs.clone(); // might have been pre-prepared, the rest we take ours + module.last_input_states = existing_mod.last_input_states.clone(); + } + modules.insert(module.name.clone(), module); + } + + Machine { + modules, + work_queue: BinaryHeap::new(), + count_low: 0, count_high: 0 + } + } +} + +impl Machine { + fn press_button(&mut self) { + self.send_pulse(PulseType::Low, "broadcaster", "button", 1); + } + fn send_pulse(&mut self, signal: PulseType, target: &str, from: &str, priority: usize) { + // count pulse when it is received + match signal { + PulseType::Low => self.count_low += 1, + PulseType::High => self.count_high += 1, + }; + match self.modules[target].kind { + Some(ModuleKind::Button) | Some(ModuleKind::Broadcast) => { + self.send_all_outputs(PulseType::Low, target, priority) + } + Some(ModuleKind::FlipFlop) => match signal { + PulseType::High => (), + PulseType::Low => { + let new_state = !self.modules.get_mut(target).unwrap().on; + self.modules.get_mut(target).unwrap().on = new_state; + self.send_all_outputs(new_state.into(), target, priority) + } + }, + 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) + { + self.send_all_outputs(PulseType::Low, target, priority) + } else { + self.send_all_outputs(PulseType::High, target, priority) + } + } + None => (), + } + } + fn send_all_outputs(&mut self, signal: PulseType, from: &str, priority: usize) { + self.work_queue.push(Job { + signal, + targets: self.modules[from].outputs.iter().map(|op| op.to_owned()).collect(), + from: from.to_owned(), + priority: priority + 1, + }) + } + fn run(&mut self) { + while let Some(job) = self.work_queue.pop() { + for target in job.targets { + self.send_pulse(job.signal, &target, &job.from, job.priority); + } + } + } +} + +// 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(); + } + machine.count_low * machine.count_high +} + +// PROBLEM 2 solution +fn problem2(input: Lines) -> u64 { + 0 +} + +#[cfg(test)] +mod tests { + use crate::*; + use std::io::Cursor; + + const EXAMPLE: &str = &"broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output"; + + #[test] + fn problem1_example() { + 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); + } +}