From 082eb03bc1ebe97173d45525c5ff6de46da6bd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:22:42 +0100 Subject: [PATCH] day 20 --- Cargo.lock | 9 ++ day-20/Cargo.toml | 13 +++ day-20/src/bin/input.txt | 58 ++++++++++ day-20/src/bin/part1.rs | 221 +++++++++++++++++++++++++++++++++++++++ day-20/src/bin/part2.rs | 179 +++++++++++++++++++++++++++++++ 5 files changed, 480 insertions(+) create mode 100644 day-20/Cargo.toml create mode 100644 day-20/src/bin/input.txt create mode 100644 day-20/src/bin/part1.rs create mode 100644 day-20/src/bin/part2.rs diff --git a/Cargo.lock b/Cargo.lock index 35720b9..1fe1126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,15 @@ dependencies = [ "nom", ] +[[package]] +name = "day-20" +version = "0.0.0" +dependencies = [ + "indoc", + "nom", + "num", +] + [[package]] name = "deprecate-until" version = "0.1.1" diff --git a/day-20/Cargo.toml b/day-20/Cargo.toml new file mode 100644 index 0000000..36b3f62 --- /dev/null +++ b/day-20/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-20" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +num = "0.4.1" + +[dev-dependencies] +indoc.workspace = true diff --git a/day-20/src/bin/input.txt b/day-20/src/bin/input.txt new file mode 100644 index 0000000..dc7efc2 --- /dev/null +++ b/day-20/src/bin/input.txt @@ -0,0 +1,58 @@ +%dt -> fm, hd +%tl -> jk, hd +%vx -> kc, sz +%sz -> kc +%kj -> tl, hd +%pm -> tb +%fc -> rt +&tb -> sx, qn, vj, qq, sk, pv +%df -> bb +%qq -> jm +%sl -> vz +broadcaster -> hp, zb, xv, qn +&pv -> kh +%gf -> pm, tb +%pb -> hd, kj +%gr -> hd, pb +%gs -> kc, pd +%tx -> df +%jm -> tb, db +%bh -> fl, gz +%rt -> kc, xp +&qh -> kh +%lb -> zm, fl +%pd -> lq +%qn -> sk, tb +%gb -> qq, tb +&xm -> kh +%mv -> hd, gr +%gz -> fl +%js -> mv +%hp -> dt, hd +%nk -> kc, vx +&kh -> rx +%zc -> tx +%mp -> js, hd +%zm -> mb +%xh -> cd, tb +%db -> xh, tb +%sx -> vj +&hz -> kh +%vj -> gb +%zq -> hd +%lj -> fc, kc +%lg -> kc, nk +&fl -> xv, tx, sl, df, qh, zc, zm +&kc -> zb, xp, pd, fc, xm +%lq -> kc, lj +&hd -> hp, js, hz +%mb -> fl, sl +%vz -> fl, bh +%fm -> mp, hd +%bb -> fl, lb +%zb -> gs, kc +%xp -> lg +%jk -> zq, hd +%xv -> zc, fl +%sk -> sx +%cd -> gf, tb \ No newline at end of file diff --git a/day-20/src/bin/part1.rs b/day-20/src/bin/part1.rs new file mode 100644 index 0000000..2c5d7db --- /dev/null +++ b/day-20/src/bin/part1.rs @@ -0,0 +1,221 @@ +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + sync::Mutex, +}; + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{self, alpha1, line_ending}, + combinator::{map, value}, + multi::separated_list1, + sequence::{pair, separated_pair}, + IResult, +}; + +fn main() { + println!("{}", part1(include_str!("./input.txt"))); +} + +fn part1(input: &str) -> usize { + let modules = input_parser(input).unwrap().1; + let conjunction_targets = modules + .iter() + .filter(|(_, mod_type, _)| mod_type == &Module::Conjunction) + .map(|(tag, _, _)| { + ( + *tag, + modules + .iter() + .filter(|(_, _, targets)| targets.contains(tag)) + .map(|(tag, _, _)| *tag) + .collect::>(), + ) + }) + .collect::>(); + + let modules = modules + .into_iter() + .map(|(tag, mod_type, targets)| { + let module = match mod_type { + Module::Broadcaster => StatefulModule::Broadcaster { targets }, + Module::FlipFlop => StatefulModule::FlipFlop { + targets, + state: Mutex::new(false), + }, + Module::Conjunction => StatefulModule::Conjunction { + targets, + state: conjunction_targets[&tag] + .iter() + .map(|tag| (*tag, Mutex::new(false))) + .collect(), + }, + }; + (tag, module) + }) + .collect::>(); + + let counter = PulseCounter::new(); + + for _ in 0..1000 { + let mut next_state = VecDeque::from(vec![("button", false, "broadcaster")]); + while let Some((prev_tag, high, tag)) = next_state.pop_front() { + counter.count(high); + if let Some(module) = modules.get(tag) { + next_state.extend( + module + .send(high, prev_tag) + .into_iter() + .map(|(t, h)| (tag, h, t)), + ); + } + } + } + + counter.get_product() +} + +type ModuleVec<'a> = Vec<(&'a str, Module, Vec<&'a str>)>; +fn input_parser(i: &str) -> IResult<&str, ModuleVec> { + map(separated_list1(line_ending, module_parser), |modules| { + modules + .into_iter() + .map(|((mod_type, tag), targets)| (tag, mod_type, targets)) + .collect::>() + })(i) +} + +fn module_parser(i: &str) -> IResult<&str, ((Module, &str), Vec<&str>)> { + separated_pair( + alt(( + value((Module::Broadcaster, "broadcaster"), tag("broadcaster")), + pair(value(Module::FlipFlop, complete::char('%')), alpha1), + pair(value(Module::Conjunction, complete::char('&')), alpha1), + )), + tag(" -> "), + separated_list1(tag(", "), alpha1), + )(i) +} + +#[derive(Debug)] +enum StatefulModule<'a, 'b, 'c> { + Broadcaster { + targets: Vec<&'a str>, + }, + FlipFlop { + targets: Vec<&'b str>, + state: Mutex, + }, + Conjunction { + targets: Vec<&'c str>, + state: BTreeMap<&'c str, Mutex>, + }, +} +impl StatefulModule<'_, '_, '_> { + fn send(&self, high: bool, prev_tag: &str) -> Vec<(&str, bool)> { + match self { + Self::Broadcaster { targets } => { + targets.iter().map(|tag| (*tag, high)).collect::>() + } + Self::FlipFlop { targets, state } => { + if high { + Vec::new() + } else { + let mut state = state.lock().unwrap(); + *state = !*state; + let high = *state; + drop(state); + targets.iter().map(|tag| (*tag, high)).collect::>() + } + } + Self::Conjunction { targets, state } => { + let mut sender_state = state[prev_tag].lock().unwrap(); + *sender_state = high; + drop(sender_state); + let all_high = state.values().all(|state| *state.lock().unwrap()); + targets + .iter() + .map(|tag| (*tag, !all_high)) + .collect::>() + } + } + } +} + +#[derive(Debug)] +struct PulseCounter { + count_low: Mutex, + count_high: Mutex, +} +impl PulseCounter { + fn new() -> Self { + Self { + count_low: Mutex::new(0), + count_high: Mutex::new(0), + } + } + fn count(&self, high: bool) { + if high { + *self.count_high.lock().unwrap() += 1; + } else { + *self.count_low.lock().unwrap() += 1; + } + } + + fn get(&self, high: bool) -> usize { + if high { + *self.count_high.lock().unwrap() + } else { + *self.count_low.lock().unwrap() + } + } + + fn get_product(&self) -> usize { + self.get(true) * self.get(false) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Module { + Broadcaster, + FlipFlop, + Conjunction, +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn test_part1_example1() { + assert_eq!( + part1(indoc!( + " + broadcaster -> a, b, c + %a -> b + %b -> c + %c -> inv + &inv -> a + " + )), + 32_000_000 + ); + } + + #[test] + fn test_part1_example2() { + assert_eq!( + part1(indoc!( + " + broadcaster -> a + %a -> inv, con + &inv -> b + %b -> con + &con -> output + " + )), + 11_687_500 + ); + } +} diff --git a/day-20/src/bin/part2.rs b/day-20/src/bin/part2.rs new file mode 100644 index 0000000..7f8164a --- /dev/null +++ b/day-20/src/bin/part2.rs @@ -0,0 +1,179 @@ +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + sync::Mutex, +}; + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{self, alpha1, line_ending}, + combinator::{map, value}, + multi::separated_list1, + sequence::{pair, separated_pair}, + IResult, +}; + +fn main() { + println!("{}", part2(include_str!("./input.txt"))); +} + +fn part2(input: &str) -> usize { + let raw_modules = input_parser(input).unwrap().1; + let conjunction_targets = raw_modules + .iter() + .filter(|(_, mod_type, _)| mod_type == &Module::Conjunction) + .map(|(tag, _, _)| { + ( + *tag, + raw_modules + .iter() + .filter(|(_, _, targets)| targets.contains(tag)) + .map(|(tag, _, _)| *tag) + .collect::>(), + ) + }) + .collect::>(); + + let modules = to_stateful_modules(&raw_modules, &conjunction_targets); + + let starts = follow_signal(("button", false, "broadcaster"), &modules); + + println!("{:?}", starts); + + starts + .iter() + .map(|(_, i)| *i) + .reduce(num::integer::lcm) + .expect("empty list") +} + +fn to_stateful_modules<'a>( + modules: &'a [(&'a str, Module, Vec<&'a str>)], + conjunction_targets: &HashMap<&str, Vec<&'a str>>, +) -> HashMap<&'a str, StatefulModule<'a, 'a, 'a>> { + modules + .iter() + .map(|(tag, mod_type, targets)| { + let module = match mod_type { + Module::Broadcaster => StatefulModule::Broadcaster { + targets: targets.clone(), + }, + Module::FlipFlop => StatefulModule::FlipFlop { + targets: targets.clone(), + state: Mutex::new(false), + }, + Module::Conjunction => StatefulModule::Conjunction { + targets: targets.clone(), + state: conjunction_targets[tag] + .iter() + .map(|tag| (*tag, Mutex::new(false))) + .collect(), + }, + }; + (*tag, module) + }) + .collect() +} + +fn follow_signal<'a>( + start: (&'a str, bool, &'a str), + modules: &'a HashMap<&'a str, StatefulModule<'a, 'a, 'a>>, +) -> Vec<(&'a str, usize)> { + let mut res = Vec::new(); + let mut i = 0; + 'button: loop { + i += 1; + let mut next_state = VecDeque::from(vec![start]); + while let Some((prev_tag, high, tag)) = next_state.pop_front() { + if !high && ["pv", "qh", "xm", "hz"].contains(&tag) { + res.push((tag, i)); + } + if res.len() >= 4 { + break 'button; + } + if let Some(module) = modules.get(tag) { + next_state.extend( + module + .send(high, prev_tag) + .into_iter() + .map(|(t, h)| (tag, h, t)), + ); + } + } + } + res +} + +type ModuleVec<'a> = Vec<(&'a str, Module, Vec<&'a str>)>; +fn input_parser(i: &str) -> IResult<&str, ModuleVec> { + map(separated_list1(line_ending, module_parser), |modules| { + modules + .into_iter() + .map(|((mod_type, tag), targets)| (tag, mod_type, targets)) + .collect::>() + })(i) +} + +fn module_parser(i: &str) -> IResult<&str, ((Module, &str), Vec<&str>)> { + separated_pair( + alt(( + value((Module::Broadcaster, "broadcaster"), tag("broadcaster")), + pair(value(Module::FlipFlop, complete::char('%')), alpha1), + pair(value(Module::Conjunction, complete::char('&')), alpha1), + )), + tag(" -> "), + separated_list1(tag(", "), alpha1), + )(i) +} + +#[derive(Debug)] +enum StatefulModule<'a, 'b, 'c> { + Broadcaster { + targets: Vec<&'a str>, + }, + FlipFlop { + targets: Vec<&'b str>, + state: Mutex, + }, + Conjunction { + targets: Vec<&'c str>, + state: BTreeMap<&'c str, Mutex>, + }, +} +impl StatefulModule<'_, '_, '_> { + fn send(&self, high: bool, prev_tag: &str) -> Vec<(&str, bool)> { + match self { + Self::Broadcaster { targets } => { + targets.iter().map(|tag| (*tag, high)).collect::>() + } + Self::FlipFlop { targets, state } => { + if high { + Vec::new() + } else { + let mut state = state.lock().unwrap(); + *state = !*state; + let high = *state; + drop(state); + targets.iter().map(|tag| (*tag, high)).collect::>() + } + } + Self::Conjunction { targets, state } => { + let mut sender_state = state[prev_tag].lock().unwrap(); + *sender_state = high; + drop(sender_state); + let all_high = state.values().all(|state| *state.lock().unwrap()); + targets + .iter() + .map(|tag| (*tag, !all_high)) + .collect::>() + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Module { + Broadcaster, + FlipFlop, + Conjunction, +}