diff --git a/day20/Cargo.lock b/day20/Cargo.lock new file mode 100644 index 0000000..dc6afe2 --- /dev/null +++ b/day20/Cargo.lock @@ -0,0 +1,92 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = [ + "num", +] + +[[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", +] diff --git a/day20/Cargo.toml b/day20/Cargo.toml new file mode 100644 index 0000000..921ac8c --- /dev/null +++ b/day20/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "day20" +version = "0.1.0" +edition = "2021" +default-run = "sane" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num = "0.4.1" + +[[bin]] +name = "oop" +path= "src/main_oop.rs" + +[[bin]] +name = "sane" +path= "src/main_sane.rs" diff --git a/day20/input.txt b/day20/input.txt new file mode 100644 index 0000000..3c14377 --- /dev/null +++ b/day20/input.txt @@ -0,0 +1,58 @@ +%vb -> ck +%pb -> xv +> -> jq +%hj -> lk, hh +%zd -> fm +%hr -> hh +%rg -> tp +%tf -> ck, tx +%pp -> vs, hh +%vx -> df +%tx -> mr, ck +%nh -> vx +%sc -> ck, vb +%cc -> ck, rj +%tn -> kz, lt +%fp -> rb +%hc -> kz +%rb -> ns, mf +%pc -> vh +broadcaster -> tf, br, zn, nc +%zn -> kz, fv +&ns -> pb, lr, br, fp, gp, gv, jl +%nc -> hh, hj +%mf -> ns, gp +%xv -> ns, kh +%rj -> ck, sc +%tg -> cc +%gp -> pb +%jz -> lj, ns +%jl -> fp +&vr -> jq +&jq -> rx +&kz -> zf, nl, df, zn, vx, nh +%gv -> jl +%gf -> zf, kz +%df -> gf +%kq -> pp +%lk -> hh, zd +%vs -> bp +%lt -> ls, kz +&nl -> jq +%mr -> rg +%zf -> qf +%br -> gv, ns +%rk -> hh, hr +%qf -> kz, tn +%rv -> rk, hh +%ls -> hc, kz +%fm -> kq +&ck -> tp, vr, pc, tg, mr, tf, rg +%fv -> nh, kz +%tp -> pc +%vh -> ck, tg +&hh -> vs, kq, gt, nc, zd, fm +&lr -> jq +%kh -> ns, jz +%bp -> hh, rv +%lj -> ns diff --git a/day20/input_part2_analysis.txt b/day20/input_part2_analysis.txt new file mode 100644 index 0000000..f0dff91 --- /dev/null +++ b/day20/input_part2_analysis.txt @@ -0,0 +1,92 @@ +The input of rx is conjunction of hh, ck, kz, ns (truth = low pulse) +&jq -> rx + +> -> jq +&vr -> jq +&nl -> jq +&lr -> jq + + +Counter 1 +&hh -> nc, zd, vs, kq, fm | -> gt + +%nc -> hj, hh | <- hh, broadcast +%hj -> lk, hh +%lk -> zd, hh +%zd -> fm | <- hh +%fm -> kq | <- hh +%kq -> pp | <- hh +%pp -> vs, hh +%vs -> bp | <- hh +%bp -> rv, hh +%rv -> rk, hh +%rk -> hr, hh +%hr -> hh + +111101000111(2) = 3911 + + +Counter 2 +&ck -> tf, mr, rg, tp, pc, tg | -> vr + +%tf -> tx, ck | <- broadcast, ck +%tx -> mr, ck +%mr -> rg | <- ck +%rg -> tp | <- ck +%tp -> pc | <- ck +%pc -> vh | <- ck +%vh -> tg, ck +%tg -> cc | <- ck +%cc -> rj, ck +%rj -> sc, ck +%sc -> vb, ck +%vb -> ck + +111101000011(2) = 3907 + + +Counter 3 +&kz -> zn, nh, vx, df, zf | -> nl + +%zn -> fv, kz | <- broadcast, kz +%fv -> nh, kz +%nh -> vx | <- kz +%vx -> df | <- kz +%df -> gf | <- kz +%gf -> zf, kz +%zf -> qf | <- kz +%qf -> tn, kz +%tn -> lt, kz +%lt -> ls, kz +%ls -> hc, kz +%hc -> kz + +111110100011(2) = 4003 + + +Counter 4 +&ns -> pb, br, fp, gp, gv, jl | -> lr + +%br -> gv, ns | <- broadcast +%gv -> jl +%jl -> fp +%fp -> rb +%rb -> mf, ns +%mf -> gp, ns +%gp -> pb +%pb -> xv +%xv -> kh, ns +%kh -> jz, ns +%jz -> lj, ns +%lj -> ns + +111100110001(2) = 3889 + +The broadcast node drives all the counters +broadcaster -> nc, tf, zn, br + +Period of rx (same as first activation) is LCM of periods of all counters +LCM = 237878264003759 + + + diff --git a/day20/src/main_oop.rs b/day20/src/main_oop.rs new file mode 100644 index 0000000..ee96f0f --- /dev/null +++ b/day20/src/main_oop.rs @@ -0,0 +1,214 @@ +use std::any::Any; +use std::cmp; +use std::collections::{HashMap, VecDeque}; +use std::fs::read_to_string; +use std::time::Instant; + +// Object-oriented, polymorphic way of implementing it, +// does less than main_sane.rs (only part 1) with more lines of code + +struct Pulse { + src: u16, + dest: u16, + high: bool, +} + +trait Module: Any { + fn handle_pulse(&mut self, pulse: Pulse, queue: &mut VecDeque); + fn add_input(&mut self, id: u16); + fn add_output(&mut self, id: u16); +} + +struct ModuleBase { + inputs: Vec, + outputs: Vec, + id: u16, +} + +impl ModuleBase { + fn new(id: u16) -> ModuleBase { + ModuleBase { + inputs: vec![], + outputs: vec![], + id: id, + } + } + + fn add_input(&mut self, id: u16) { + self.inputs.push(id); + } + + fn add_output(&mut self, id: u16) { + self.outputs.push(id); + } +} + +struct BroadcastModule { + base: ModuleBase, +} + +impl BroadcastModule { + fn new(id: u16) -> BroadcastModule { + BroadcastModule { + base: ModuleBase::new(id) + } + } +} + +impl Module for BroadcastModule { + fn handle_pulse(&mut self, pulse: Pulse, queue: &mut VecDeque) { + queue.extend(self.base.outputs.iter().map( + |&id| Pulse { src: self.base.id, dest: id, high: pulse.high })); + } + + fn add_input(&mut self, id: u16) { + self.base.add_input(id); + } + + fn add_output(&mut self, id: u16) { + self.base.add_output(id); + } +} + +struct FlipFlopModule { + base: ModuleBase, + on: bool, +} + +impl FlipFlopModule { + fn new(id: u16) -> FlipFlopModule { + FlipFlopModule { + base: ModuleBase::new(id), + on: false, + } + } +} + +impl Module for FlipFlopModule { + fn handle_pulse(&mut self, pulse: Pulse, queue: &mut VecDeque) { + if !pulse.high { + self.on = !self.on; + queue.extend(self.base.outputs.iter().map( + |&id| Pulse { src: self.base.id, dest: id, high: self.on })); + } + } + + fn add_input(&mut self, id: u16) { + self.base.add_input(id); + } + + fn add_output(&mut self, id: u16) { + self.base.add_output(id); + } +} + +struct ConjunctionModule { + base: ModuleBase, + memory: Vec, +} + +impl ConjunctionModule { + fn new(id: u16) -> ConjunctionModule { + ConjunctionModule { + base: ModuleBase::new(id), + memory: vec![], + } + } +} + +impl Module for ConjunctionModule { + fn handle_pulse(&mut self, pulse: Pulse, queue: &mut VecDeque) { + if let Some(pos) = self.base.inputs.iter().position(|&id| id == pulse.src) { + self.memory[pos] = pulse.high; + let out_high = !self.memory.iter().all(|&v| v); + queue.extend(self.base.outputs.iter().map( + |&id| Pulse { src: self.base.id, dest: id, high: out_high })); + } + } + + fn add_input(&mut self, id: u16) { + self.base.add_input(id); + self.memory.push(false); + } + + fn add_output(&mut self, id: u16) { + self.base.add_output(id); + } +} + +fn encode_module_name(name: &str) -> u16 { + let bytes = &name.as_bytes()[0..cmp::min(name.as_bytes().len(), 3)]; + let mut acc = 0u16; + for &c in bytes { + acc = acc * 26 + (c - b'a') as u16; + } + acc +} + +fn main() { + let time_start = Instant::now(); + let input_str = read_to_string("input.txt").unwrap(); + let time_start_no_io = Instant::now(); + let mut modules: HashMap> = HashMap::new(); + // let rx_id = encode_module_name("rx"); + // modules.insert(rx_id, Box::new(FlipFlopModule::new(rx_id))); + for line in input_str.lines() { + let mut split_whitespace = line.split_whitespace(); + let name = split_whitespace.next().unwrap(); + let new_module: Box; + let id = encode_module_name(&name[1..]); + match name.as_bytes()[0] { + b'%' => new_module = Box::new(FlipFlopModule::new(id)), + b'&' => new_module = Box::new(ConjunctionModule::new(id)), + _ => new_module = Box::new(BroadcastModule::new(id)) + } + modules.insert(id, new_module); + } + for line in input_str.lines() { + let mut split = line.split([' ', ',']). + filter(|&v| !v.is_empty()); + let name = split.next().unwrap(); + let in_id = encode_module_name(&name[1..]); + split.next(); + while let Some(name) = split.next() { + let id = encode_module_name(name); + modules.get_mut(&in_id).unwrap().add_output(id); + match modules.get_mut(&id) { + None => {} + Some(in_module) => { + in_module.add_input(in_id); + } + } + } + } + let broadcast_id = encode_module_name("roadcast"); + let mut low_pulses = 0u64; + let mut high_pulses = 0u64; + let mut queue: VecDeque = VecDeque::new(); + for _ in 0..1000 { + let mut low_pulses_step = 0; + let mut high_pulses_step = 0; + queue.push_back(Pulse {src: u16::MAX, dest: broadcast_id, high: false}); + while !queue.is_empty() { + let pulse = queue.pop_front().unwrap(); + match pulse.high { + true => high_pulses_step += 1, + false => low_pulses_step += 1 + } + match modules.get_mut(&pulse.dest) { + None => {} + Some(module) => { + module.handle_pulse(pulse, &mut queue); + } + } + } + low_pulses += low_pulses_step; + high_pulses += high_pulses_step; + } + let result1 = low_pulses * high_pulses; + let elapsed = time_start.elapsed().as_micros(); + let elapsed_no_io = time_start_no_io.elapsed().as_micros(); + println!("Time: {}us", elapsed); + println!("Time without file i/o: {}us", elapsed_no_io); + println!("Result1: {}", result1); +} diff --git a/day20/src/main_sane.rs b/day20/src/main_sane.rs new file mode 100644 index 0000000..0b8e5d9 --- /dev/null +++ b/day20/src/main_sane.rs @@ -0,0 +1,192 @@ +use std::cmp; +use std::collections::{HashMap, VecDeque}; +use std::fs::read_to_string; +use std::time::Instant; +use num::integer::lcm; + +struct Pulse { + src: u16, + dest: u16, + high: bool, +} + +enum ModuleKind { + Broadcast, + FlipFlop(bool), + Conjunction(Vec), +} + +struct Module { + inputs: Vec, + outputs: Vec, + id: u16, + kind: ModuleKind, +} + +impl Module { + fn new(id: u16, kind: ModuleKind) -> Module { + Module { + inputs: vec![], + outputs: vec![], + id, + kind, + } + } + + fn new_broadcast(id: u16) -> Module { + Module::new(id, ModuleKind::Broadcast) + } + + fn new_flipflop(id: u16) -> Module { + Module::new(id, ModuleKind::FlipFlop(false)) + } + + fn new_conjunction(id: u16) -> Module { + Module::new(id, ModuleKind::Conjunction(vec![])) + } + + fn add_input(&mut self, id: u16) { + self.inputs.push(id); + match self.kind { + ModuleKind::Conjunction(ref mut memory) => { + memory.push(false); + } + _ => {} + } + } + + fn add_output(&mut self, id: u16) { + self.outputs.push(id); + } + + fn handle_pulse(&mut self, pulse: Pulse, queue: &mut VecDeque) { + match self.kind { + ModuleKind::Broadcast => { + queue.extend(self.outputs.iter().map( + |&id| Pulse { src: self.id, dest: id, high: pulse.high })); + } + ModuleKind::FlipFlop(ref mut on) => { + if !pulse.high { + *on = !*on; + queue.extend(self.outputs.iter().map( + |&id| Pulse { src: self.id, dest: id, high: *on })); + } + } + ModuleKind::Conjunction(ref mut memory) => { + if let Some(pos) = self.inputs.iter().position(|&id| id == pulse.src) { + memory[pos] = pulse.high; + let out_high = !memory.iter().all(|&v| v); + queue.extend(self.outputs.iter().map( + |&id| Pulse { src: self.id, dest: id, high: out_high })); + } + } + } + } +} + +fn encode_module_name(name: &str) -> u16 { + let bytes = &name.as_bytes()[0..cmp::min(name.as_bytes().len(), 3)]; + let mut acc = 0u16; + for &c in bytes { + acc = acc * 26 + (c - b'a') as u16; + } + acc +} + +fn main() { + let time_start = Instant::now(); + let input_str = read_to_string("input.txt").unwrap(); + let time_start_no_io = Instant::now(); + let mut modules: HashMap = HashMap::new(); + // let rx_id = encode_module_name("rx"); + // modules.insert(rx_id, Module::new_flipflop(rx_id)); + for line in input_str.lines() { + let mut split_whitespace = line.split_whitespace(); + let name = split_whitespace.next().unwrap(); + let new_module: Module; + let id = encode_module_name(&name[1..]); + match name.as_bytes()[0] { + b'%' => new_module = Module::new_flipflop(id), + b'&' => new_module = Module::new_conjunction(id), + _ => new_module = Module::new_broadcast(id) + } + modules.insert(id, new_module); + } + for line in input_str.lines() { + let mut split = line.split([' ', ',']). + filter(|&v| !v.is_empty()); + let name = split.next().unwrap(); + let in_id = encode_module_name(&name[1..]); + split.next(); + while let Some(name) = split.next() { + let id = encode_module_name(name); + modules.get_mut(&in_id).unwrap().add_output(id); + match modules.get_mut(&id) { + None => {} + Some(in_module) => { + in_module.add_input(in_id); + } + } + } + } + // Part 1 + let broadcast_id = encode_module_name("roadcast"); + let mut low_pulses = 0u64; + let mut high_pulses = 0u64; + let mut queue: VecDeque = VecDeque::new(); + for _ in 0..1000 { + let mut low_pulses_step = 0; + let mut high_pulses_step = 0; + queue.push_back(Pulse { src: u16::MAX, dest: broadcast_id, high: false }); + while !queue.is_empty() { + let pulse = queue.pop_front().unwrap(); + match pulse.high { + true => high_pulses_step += 1, + false => low_pulses_step += 1 + } + match modules.get_mut(&pulse.dest) { + None => {} + Some(module) => { + module.handle_pulse(pulse, &mut queue); + } + } + } + low_pulses += low_pulses_step; + high_pulses += high_pulses_step; + } + let result1 = low_pulses * high_pulses; + // Part 2 + // Use the knowledge of the network, that it is that broadcast drives a series of counters + let mut counter_periods: Vec = vec![]; + for &node in &modules.get(&broadcast_id).unwrap().outputs { + let mut period = 0u16; + let mut current_node = node; + let mut bits = 0; + loop { + bits += 1; + let mut next_id = current_node; + period >>= 1; + let outputs = &modules.get(¤t_node).unwrap().outputs; + for &output_id in outputs { + match modules.get(&output_id).unwrap().kind { + ModuleKind::Conjunction(_) => period |= 0x8000, + _ => next_id = output_id + } + } + if next_id == current_node { + break; + } else { + current_node = next_id; + } + } + period >>= 16 - bits; + counter_periods.push(period as u64); + } + let result2 = counter_periods.iter().fold(1u64, |acc, &v| lcm(acc, v)); + let elapsed = time_start.elapsed().as_micros(); + let elapsed_no_io = time_start_no_io.elapsed().as_micros(); + println!("Time: {}us", elapsed); + println!("Time without file i/o: {}us", elapsed_no_io); + println!("Result1: {}", result1); + println!("Result2: {}", result2); +}