diff --git a/2023/Cargo.toml b/2023/Cargo.toml index 21401dd..07c51de 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -21,7 +21,7 @@ members = [ "day17", "day18", "day19", - # "day20", + "day20", # "day21", # "day22", # "day23", diff --git a/2023/day19/map+sum b/2023/day19/map+sum deleted file mode 100644 index e69de29..0000000 diff --git a/2023/day20/.gitignore b/2023/day20/.gitignore new file mode 100644 index 0000000..e983cca --- /dev/null +++ b/2023/day20/.gitignore @@ -0,0 +1,19 @@ +input.txt +flamegraph.svg +perf.data* +### Rust +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + diff --git a/2023/day20/Cargo.toml b/2023/day20/Cargo.toml new file mode 100644 index 0000000..7317983 --- /dev/null +++ b/2023/day20/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day20" +authors = ["mirsella "] +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + diff --git a/2023/day20/build.rs b/2023/day20/build.rs new file mode 100644 index 0000000..b6a19a0 --- /dev/null +++ b/2023/day20/build.rs @@ -0,0 +1,28 @@ +use std::fs::File; +use std::io::{self, Read}; +use std::path::PathBuf; +use std::{env, fs}; + +fn replace_in_file(file_path: &PathBuf, old: &str, new: &str) -> io::Result<()> { + let mut contents = String::new(); + File::open(file_path)?.read_to_string(&mut contents)?; + let new_contents = contents.replace(old, new); + if contents != new_contents { + println!("Updating {}", file_path.display()); + fs::write(file_path, new_contents)?; + } + Ok(()) +} + +fn main() -> io::Result<()> { + let pkg_name = env::var("CARGO_PKG_NAME").unwrap(); + replace_in_file( + &"../Cargo.toml".into(), + &format!("# \"{pkg_name}\""), + &format!("\"{pkg_name}\""), + )?; + + replace_in_file(&"./Cargo.toml".into(), "\n[workspace]", "")?; + + Ok(()) +} diff --git a/2023/day20/src/main.rs b/2023/day20/src/main.rs new file mode 100644 index 0000000..f1037b3 --- /dev/null +++ b/2023/day20/src/main.rs @@ -0,0 +1,260 @@ +use std::{ + any::Any, + borrow::BorrowMut, + cell::{Cell, RefCell}, + collections::{HashMap, VecDeque}, + iter, + ops::{Add, Not}, + rc::Rc, +}; + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +enum Pulse { + High, + Low, +} +impl Add for (usize, usize) { + type Output = (usize, usize); + fn add(self, pulse: Pulse) -> Self::Output { + match pulse { + Pulse::High => (self.0, self.1 + 1), + Pulse::Low => (self.0 + 1, self.1), + } + } +} +impl From for bool { + fn from(p: Pulse) -> Self { + match p { + Pulse::High => true, + Pulse::Low => false, + } + } +} +impl From for Pulse { + fn from(b: bool) -> Self { + match b { + true => Self::High, + false => Self::Low, + } + } +} +type Modules<'a> = Vec>>; +type PulseResult<'a> = (Pulse, &'a [&'a str]); +trait Module<'a> { + fn pulse(&self, sender: &'a str, input: Pulse) -> PulseResult; + fn name(&self) -> &'a str; + fn as_any(&self) -> &(dyn Any + 'a); +} +struct Broadcaster<'a> { + outputs: Vec<&'a str>, + name: &'a str, +} +impl<'a> Module<'a> for Broadcaster<'a> { + fn pulse(&self, _: &str, input: Pulse) -> PulseResult { + (input, &self.outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Broadcaster<'a> { + fn new(name: &'a str, outputs: Vec<&'a str>) -> Self { + Self { name, outputs } + } +} +struct Dummy<'a> { + name: &'a str, +} +impl<'a> Module<'a> for Dummy<'a> { + fn pulse(&self, _: &str, _input: Pulse) -> PulseResult { + (Pulse::Low, &[]) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Dummy<'a> { + fn new(name: &'a str) -> Self { + Self { name } + } +} +struct Flipflop<'a> { + state: Cell, + outputs: Vec<&'a str>, + name: &'a str, +} +impl<'a> Module<'a> for Flipflop<'a> { + fn pulse(&self, _: &str, input: Pulse) -> PulseResult { + if let Pulse::High = input { + return (Pulse::Low, &[]); + } + match self.state.get() { + true => self.state.set(false), + false => self.state.set(true), + } + let state = self.state.get().into(); + (state, &self.outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Flipflop<'a> { + fn new(name: &'a str, outputs: Vec<&'a str>) -> Self { + Self { + name, + outputs, + state: Cell::new(false), + } + } +} +struct Conjunction<'a> { + name: &'a str, + inputs: RefCell>, + outputs: Vec<&'a str>, +} +impl<'a> Module<'a> for Conjunction<'a> { + fn pulse(&self, sender: &'a str, input: Pulse) -> PulseResult { + let all_high = { + self.inputs.borrow_mut().insert(sender, input); + self.inputs.borrow().values().all(|&p| p == Pulse::High) + // all high -> low + // mixed -> high + // all low -> high + }; + let pulse = all_high.not().into(); + (pulse, &self.outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Conjunction<'a> { + fn new(name: &'a str, outputs: Vec<&'a str>, inputs: Vec<&'a str>) -> Self { + Self { + name, + inputs: RefCell::new(HashMap::from_iter( + inputs.into_iter().map(|i| (i, Pulse::Low)), + )), + outputs, + } + } +} + +fn new_module<'a>( + name: &'a str, + outputs: Vec<&'a str>, + inputs: Vec<&'a str>, +) -> Box + 'a> { + if name == "broadcaster" { + return Box::new(Broadcaster::new(name, outputs)); + } + if name.starts_with('%') { + return Box::new(Flipflop::new(name.strip_prefix('%').unwrap(), outputs)); + } + if name.starts_with('&') { + return Box::new(Conjunction::new( + name.strip_prefix('&').unwrap(), + outputs, + inputs, + )); + } + return Box::new(Dummy::new(name)); +} + +fn parse(input: &'static str) -> Vec + 'static>> { + input + .lines() + .map(|line| { + let mut s = line.split_whitespace(); + let name = s.next().unwrap().trim(); + + let outputs = s + .skip(1) + .map(|c| c.trim_matches([' ', ','].as_ref())) + .collect(); + + let inputs = input + .lines() + .filter_map(|line| { + let s = line.split_once("->").unwrap(); + if !s.1.contains(name.trim_matches([' ', '&', '%'].as_ref())) { + return None; + } + Some(s.0.trim_matches([' ', '&', '%'].as_ref())) + }) + .collect(); + new_module(name, outputs, inputs) + }) + .collect::>() +} + +fn press_button<'a>(modules: &Modules<'a>) -> (usize, usize) { + let mut queue: VecDeque<(&'a str, PulseResult)> = VecDeque::new(); + let mut counter = (1, 0); + let broadcaster = modules.iter().find(|m| m.name() == "broadcaster").unwrap(); + queue.push_back((broadcaster.name(), broadcaster.pulse("button", Pulse::Low))); + while let Some((name, (pulse, outputs))) = queue.pop_front() { + for output in outputs { + counter = counter + pulse; + if let Some(module) = modules.iter().find(|&m| m.name() == *output) { + queue.push_back((module.name(), module.pulse(name, pulse))); + } + } + } + counter +} + +fn part1(input: &'static str) -> usize { + let modules = parse(input); + let count = (0..1000) + .map(|_| press_button(&modules)) + .fold((0, 0), |a, b| (a.0 + b.0, a.1 + b.1)); + count.0 * count.1 +} +fn part2(input: &str) -> usize { + todo!() +} +fn main() { + let input: &str = include_str!("../input.txt"); + println!("Part 1: {}", part1(input)); + println!("Part 2: {}", part2(input)); +} + +#[cfg(test)] +mod tests { + const INPUT: &str = "broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a"; + const INPUT2: &str = "broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output"; + #[test] + fn part1() { + assert_eq!(super::part1(INPUT), 8000 * 4000); + } + #[test] + fn part1_2() { + assert_eq!(super::part1(INPUT2), 4250 * 2750); + } + #[test] + fn part2() { + assert_eq!(super::part2(INPUT), 0); + } +} diff --git a/2023/day20/src/recursive_version/main.rs b/2023/day20/src/recursive_version/main.rs new file mode 100644 index 0000000..53d423c --- /dev/null +++ b/2023/day20/src/recursive_version/main.rs @@ -0,0 +1,295 @@ +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::HashMap, + iter, + ops::Add, + rc::Rc, +}; + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +enum Pulse { + High, + Low, +} +impl Add for (usize, usize) { + type Output = (usize, usize); + fn add(self, pulse: Pulse) -> Self::Output { + match pulse { + Pulse::High => (self.0, self.1 + 1), + Pulse::Low => (self.0 + 1, self.1), + } + } +} +impl From for bool { + fn from(p: Pulse) -> Self { + match p { + Pulse::High => true, + Pulse::Low => false, + } + } +} +impl From for Pulse { + fn from(b: bool) -> Self { + match b { + true => Self::High, + false => Self::Low, + } + } +} +type Outputs<'a> = Vec>>>; +trait Module<'a> { + /// 0 low, 1 high + fn pulse(&self, sender: &'a str, input: Pulse) -> (usize, usize); + fn connect(&self, output: Outputs<'a>); + fn name(&self) -> &'a str; + fn as_any(&self) -> &(dyn Any + 'a); +} +struct Broadcaster<'a> { + outputs: RefCell>, + name: &'a str, +} +impl<'a> Module<'a> for Broadcaster<'a> { + fn pulse(&self, _: &str, input: Pulse) -> (usize, usize) { + self.outputs + .borrow() + .iter() + .map(|m| { + println!("{} -{input:?}-> {}", self.name, m.name()); + m.pulse(self.name, input) + }) + .fold((0, 0), |(low, high), (l, h)| (low + l, high + h) + input) + } + fn connect(&self, outputs: Outputs<'a>) { + self.outputs.borrow_mut().extend(outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Broadcaster<'a> { + fn new(name: &'a str) -> Self { + Self { + name, + outputs: RefCell::new(Vec::new()), + } + } +} +struct Dummy<'a> { + name: &'a str, +} +impl<'a> Module<'a> for Dummy<'a> { + fn pulse(&self, _: &str, _input: Pulse) -> (usize, usize) { + (0, 0) + } + fn connect(&self, _outputs: Outputs<'a>) {} + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Dummy<'a> { + fn new(name: &'a str) -> Self { + Self { name } + } +} +struct Flipflop<'a> { + state: Cell, + outputs: RefCell>, + name: &'a str, +} +impl<'a> Module<'a> for Flipflop<'a> { + fn pulse(&self, _: &str, input: Pulse) -> (usize, usize) { + if let Pulse::High = input { + return (0, 0); + } + match self.state.get() { + true => self.state.set(false), + false => self.state.set(true), + } + let state = self.state.get().into(); + self.outputs + .borrow() + .iter() + .map(|m| { + println!("{} -{state:?}-> {}", self.name, m.name()); + m.pulse(self.name, state) + }) + .fold((0, 0), |(low, high), (l, h)| (low + l, high + h) + state) + } + fn connect(&self, outputs: Outputs<'a>) { + self.outputs.borrow_mut().extend(outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Flipflop<'a> { + fn new(name: &'a str) -> Self { + Self { + name, + state: Cell::new(false), + outputs: RefCell::new(Vec::new()), + } + } +} +struct Conjunction<'a> { + name: &'a str, + inputs: RefCell>, + outputs: RefCell>, +} +impl<'a> Module<'a> for Conjunction<'a> { + fn pulse(&self, sender: &'a str, input: Pulse) -> (usize, usize) { + let pulse = { + let mut map = self.inputs.borrow_mut(); + map.insert(sender, input); + map.values().all(|&p| p == Pulse::Low) + }; + let pulse = pulse.into(); + self.outputs + .borrow() + .iter() + .map(|m| { + println!("{} -{pulse:?}-> {}", self.name, m.name()); + m.pulse(self.name, pulse) + }) + .fold((0, 0), |(low, high), (l, h)| (low + l, high + h) + pulse) + } + fn connect(&self, outputs: Outputs<'a>) { + self.outputs.borrow_mut().extend(outputs) + } + fn name(&self) -> &'a str { + self.name + } + fn as_any(&self) -> &(dyn Any + 'a) { + self + } +} +impl<'a> Conjunction<'a> { + fn new(name: &'a str) -> Self { + Self { + name, + inputs: RefCell::new(HashMap::new()), + outputs: RefCell::new(Vec::new()), + } + } + fn populate(&self, inputs: impl Iterator) { + let mut map = self.inputs.borrow_mut(); + for input in inputs { + map.insert(input, Pulse::Low); + } + } +} + +fn new_module<'a>(name: &'a str) -> Box + 'a> { + if name == "broadcaster" { + return Box::new(Broadcaster::new(name)); + } + if name.starts_with('%') { + return Box::new(Flipflop::new(name.strip_prefix('%').unwrap())); + } + if name.starts_with('&') { + return Box::new(Conjunction::new(name.strip_prefix('&').unwrap())); + } + return Box::new(Dummy::new(name)); +} + +fn parse(input: &'static str) -> Vec + 'static>>> { + let mut conjunctions = Vec::new(); + + let (modules, outputs) = input + .lines() + .map(|line| { + let mut s = line.split_whitespace(); + let name = s.next().unwrap().trim(); + let outputs = s + .skip(1) + .map(|c| c.trim_matches([' ', ','].as_ref())) + .collect::>(); + let module = Rc::new(new_module(name)); + if name.starts_with('&') { + conjunctions.push(module.clone()); + } + (module, outputs) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + + for (module, outputs) in modules.iter().zip(outputs) { + let outputs_modules = outputs + .iter() + .map(|&name| { + modules + .iter() + .find(|m| m.name() == name) + .cloned() + .unwrap_or_else(|| Rc::new(new_module(name))) + }) + .collect::>(); + module.connect(outputs_modules); + for con in &conjunctions { + if outputs.contains(&con.name()) { + println!("adding {} to {} inputs", module.name(), con.name()); + con.as_any() + .downcast_ref::() + .unwrap() + .populate(iter::once(module.name())); + } + } + } + modules +} + +fn part1(input: &'static str) -> usize { + let modules = parse(input); + let broadcaster = modules.iter().find(|m| m.name() == "broadcaster").unwrap(); + let count = (0..4) + .map(|_| { + println!("button -Low-> broadcaster"); + broadcaster.pulse("button", Pulse::Low) + }) + .fold((0, 0), |(low, high), (l, h)| (low + l + 1, high + h)); + dbg!(count); + count.0 * count.1 +} +fn part2(input: &str) -> usize { + todo!() +} +fn main() { + let input: &str = include_str!("../input.txt"); + println!("Part 1: {}", part1(input)); + println!("Part 2: {}", part2(input)); +} + +#[cfg(test)] +mod tests { + const INPUT: &str = "broadcaster -> a, b, c +%a -> b +%b -> c +%c -> inv +&inv -> a"; + const INPUT2: &str = "broadcaster -> a +%a -> inv, con +&inv -> b +%b -> con +&con -> output"; + #[test] + fn part1() { + assert_eq!(super::part1(INPUT), 8000 * 4000); + } + #[test] + fn part1_2() { + assert_eq!(super::part1(INPUT2), 4250 * 2750); + } + #[test] + fn part2() { + assert_eq!(super::part2(INPUT), 0); + } +}