Skip to content

Commit

Permalink
[2024] Cleanup day 19
Browse files Browse the repository at this point in the history
  • Loading branch information
connorslade committed Dec 19, 2024
1 parent 01ad458 commit 04fd360
Showing 1 changed file with 50 additions and 80 deletions.
130 changes: 50 additions & 80 deletions aoc_2024/src/day_19.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
needed: Vec<String>,
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)]
Expand Down

0 comments on commit 04fd360

Please sign in to comment.