Solution Day 19
This commit is contained in:
parent
11999d177e
commit
ba21e5fdb0
4 changed files with 138 additions and 0 deletions
7
2024/day19/Cargo.lock
generated
Normal file
7
2024/day19/Cargo.lock
generated
Normal 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
6
2024/day19/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "day19"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
25
2024/day19/README.txt
Normal file
25
2024/day19/README.txt
Normal 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
100
2024/day19/src/main.rs
Normal 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();
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue