Solution Day 19

This commit is contained in:
Markus Brueckner 2024-12-20 15:02:24 +01:00
parent 11999d177e
commit ba21e5fdb0
4 changed files with 138 additions and 0 deletions

7
2024/day19/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day19"
version = "0.1.0"

6
2024/day19/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "day19"
version = "0.1.0"
edition = "2021"
[dependencies]

25
2024/day19/README.txt Normal file
View file

@ -0,0 +1,25 @@
# Solution Day 19
The full solutions are getting rarer and rarer. Today's topic was building strings from a fixed set of substrings.
## Task 1
The initial solution to task 1 stopped after finding one solution (see the commented out code for `is_pattern_possible(...)`).
My main idea was checking, whether a substring (towel) started the pattern and then recursively check whether the remaining
pattern could also be formed. If so, the pattern would be valid.
Initially I ran into an endless loop with one of my inputs. Reason being: the recursive check lead to a situation, where the
implementation would check the same invalid suffix again and again, every time finding a way to start it, but not finish it.
I could solve this by tracking the known bad suffixes in a separate `HashMap` and short-circuiting before entering the
recursive loop again.
## Task 2
Task 2 looks like a slight extension of Task 1 (just count all combinations instead of stopping at the first), but it proved
to be much harder initially. With my approach of tracking the known bad suffixes, I simply could not prevent the endless
loop even at the first input. Even now I'm not 100% sure, why. I'm not even sure, whether this is really an endless loop or just
very, very inefficient checking the same suffix again and again.
After some more thought I decided to switch to a memoization technique: for each string store the number of known solutions and
short-circuit when you already know the solution. This did the trick and I got my solution. This way I'm even able to express
Task 1 in terms of the solver for Task 2: a valid pattern is any pattern, where the number of possible combinations is greater 0.

100
2024/day19/src/main.rs Normal file
View file

@ -0,0 +1,100 @@
use std::collections::{HashMap, HashSet};
fn load_input() -> (Vec<String>, Vec<String>) {
let mut data = include_str!("../input.txt").lines();
let mut available_towels: Vec<String> = data
.next()
.unwrap()
.split(", ")
.map(|s| s.trim().to_string())
.filter(|s| s.len() > 0)
.collect();
available_towels.sort_by_key(|s| 100 - s.len());
data.next();
let desired_patterns: Vec<String> = data.map(|s| s.to_string()).collect();
(available_towels, desired_patterns)
}
// fn is_pattern_possible(
// desired_pattern: &str,
// available_towels: &Vec<String>,
// known_bad: &mut HashSet<String>, // known bad patterns that cannot be built
// ) -> bool {
// if known_bad.contains(desired_pattern) {
// return false;
// }
// for towel in available_towels {
// if desired_pattern.starts_with(towel) {
// if desired_pattern.len() == towel.len() {
// return true; // complete match, we found something
// }
// if is_pattern_possible(&desired_pattern[towel.len()..], available_towels, known_bad) {
// return true;
// }
// }
// }
// known_bad.insert(desired_pattern.to_string());
// return false;
// }
fn find_possible_solutions(
desired_pattern: &str,
available_towels: &Vec<String>,
known_solutions: &mut HashMap<String, usize>,
) -> usize {
if let Some(known) = known_solutions.get(desired_pattern) {
return *known;
}
let mut possible_solutions = 0;
for towel in available_towels {
if desired_pattern.starts_with(towel) {
if desired_pattern.len() == towel.len() {
// exact match, count and continue
possible_solutions += 1;
} else {
possible_solutions += find_possible_solutions(
&desired_pattern[towel.len()..],
&available_towels,
known_solutions,
);
}
}
}
known_solutions.insert(desired_pattern.to_string(), possible_solutions);
possible_solutions
}
fn task1() {
let (available_towels, desired_patterns) = load_input();
let total_possible_patterns = desired_patterns
.iter()
.filter(|&pattern| {
find_possible_solutions(&pattern, &available_towels, &mut HashMap::new()) > 0
})
.count();
println!("Task 1: possible patterns: {total_possible_patterns}");
}
fn task2() {
let (available_towels, desired_patterns) = load_input();
let total_possible_combinations = desired_patterns.iter().fold(0, |existing, pattern| {
existing + find_possible_solutions(&pattern, &available_towels, &mut HashMap::new())
});
println!("Task 2: possible combinations: {total_possible_combinations}");
}
fn main() {
task1();
task2();
}