From 04fd36038b01553156148f1536828ea5cfe16791 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Thu, 19 Dec 2024 00:30:28 -0500 Subject: [PATCH] [2024] Cleanup day 19 --- aoc_2024/src/day_19.rs | 130 ++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 80 deletions(-) diff --git a/aoc_2024/src/day_19.rs b/aoc_2024/src/day_19.rs index e2f9688..a82605f 100644 --- a/aoc_2024/src/day_19.rs +++ b/aoc_2024/src/day_19.rs @@ -5,102 +5,72 @@ use common::{solution, Answer}; solution!("Linen Layout", 19); fn part_a(input: &str) -> Answer { - let problem = Problem::parse(input); - let mut sum = 0; - - for i in 0..problem.needed.len() { - if problem.possible(i) { - sum += 1; - } - } - - sum.into() + let problem = Onsen::parse(input); + problem.possible().into() } fn part_b(input: &str) -> Answer { - let problem = Problem::parse(input); - let mut sum = 0; - - for i in 0..problem.needed.len() { - sum += problem.ways(i); - } - - sum.into() + let problem = Onsen::parse(input); + problem.ways().into() } -struct Problem { - sources: Vec, - needed: Vec, +struct Onsen<'a> { + segments: Vec<&'a str>, + towels: Vec<&'a str>, } -impl Problem { - fn parse(input: &str) -> Self { +impl<'a> Onsen<'a> { + fn parse(input: &'a str) -> Self { let (sources, needed) = input.split_once("\n\n").unwrap(); - let sources = sources.split(", ").map(|x| x.to_owned()).collect(); - let needed = needed.lines().map(|x| x.to_owned()).collect(); + let segments = sources.split(", ").collect(); + let towels = needed.lines().collect(); - Self { sources, needed } + Self { segments, towels } } - fn possible(&self, design: usize) -> bool { - fn _inner<'a>( - memo: &mut HashMap<&'a str, bool>, - expected: &'a str, - sources: &[String], - ) -> bool { - if let Some(&cache) = memo.get(expected) { - return cache; - } - - if expected.len() == 0 { - memo.insert(expected, true); - return true; - } - - for source in sources { - if expected.len() >= source.len() - && expected.starts_with(source) - && _inner(memo, &expected[source.len()..], &sources) - { - memo.insert(expected, true); - return true; - } - } - - memo.insert(expected, false); - false - } + /// Returns the number of possible towel designs by counting all the towels + /// that can be made a non-zero number of ways. + fn possible(&self) -> usize { + self.towels + .iter() + .filter(|x| count_ways(&mut HashMap::new(), x, &self.segments) != 0) + .count() + } - _inner(&mut HashMap::new(), &self.needed[design], &self.sources) + /// Here we just sum up the number of ways each towel can be made. + fn ways(&self) -> u64 { + self.towels + .iter() + .map(|x| count_ways(&mut HashMap::new(), x, &self.segments)) + .sum() } +} - fn ways(&self, design: usize) -> u64 { - fn _inner<'a>( - memo: &mut HashMap<&'a str, u64>, - expected: &'a str, - sources: &[String], - ) -> u64 { - if let Some(&cache) = memo.get(expected) { - return cache; - } - - if expected.len() == 0 { - return 1; - } - - let mut ways = 0; - for source in sources { - if expected.len() >= source.len() && expected.starts_with(source) { - ways += _inner(memo, &expected[source.len()..], &sources); - } - } - - memo.insert(expected, ways); - ways - } +fn count_ways<'a>(memo: &mut HashMap<&'a str, u64>, expected: &'a str, sources: &[&'a str]) -> u64 { + if let Some(&cache) = memo.get(expected) { + return cache; + } - _inner(&mut HashMap::new(), &self.needed[design], &self.sources) + // If there is no more towel to find designs for, we have found one way to + // make the towel. + if expected.is_empty() { + return 1; } + + // Otherwise, we will sum up the number of ways the towel can be made from + // adding each of the available segments to the current towel, but only the + // ones that match the current pattern. + let mut ways = 0; + for source in sources { + if expected.len() >= source.len() && expected.starts_with(source) { + ways += count_ways(memo, &expected[source.len()..], sources); + } + } + + // Memoization!!! This is what allows us to avoid evaluating huge segments + // of the tree and get good performance. + memo.insert(expected, ways); + ways } #[cfg(test)]