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