solved day 19 part 2 with claude

This commit is contained in:
timeshifter
2026-06-25 19:47:28 +02:00
parent 1fc8dd88e9
commit b80d109839
3 changed files with 395 additions and 1 deletions
+70
View File
@@ -2,6 +2,76 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "day19" name = "day19"
version = "0.1.0" version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"rand_core",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
"chacha20",
"getrandom",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+1
View File
@@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
rand = "*"
+324 -1
View File
@@ -1,3 +1,326 @@
use std::collections::HashSet;
use rand::{
prelude::ThreadRng,
seq::{IndexedRandom, SliceRandom},
};
fn main() { fn main() {
println!("Hello, world!"); let content = std::fs::read_to_string("input.txt").unwrap();
let lines = content.lines();
let conversions: &Vec<_> = &lines
.clone()
.take_while(|l| !l.is_empty())
.map(|l| {
let mut iter = l.split_whitespace();
let a = iter.next().unwrap();
let b = iter.next_back().unwrap();
(a, b)
})
.collect();
let formula = lines.skip_while(|l| !l.is_empty()).nth(1).unwrap();
let mut capital_idxs: Vec<_> = formula
.chars()
.enumerate()
.flat_map(|(i, c)| c.is_uppercase().then_some(i))
.collect();
capital_idxs.push(formula.len()); // otherwise we are missing the last chemical
let formula_chars: Vec<_> = formula.chars().collect();
let separate: Vec<_> = capital_idxs
.iter()
.zip(capital_idxs.iter().skip(1))
.map(|(a, b)| formula_chars[*a..*b].iter().collect::<String>())
.collect();
part1(conversions, &separate); // 576
main2();
}
// fn _part2(conversions: &[(&str, &str)], formula: &str) {
// let mut rng = rand::rng();
// let mut rand_conversions = conversions.to_vec();
// let mut old_conversion_count = 10000; // large
// let mut conversion_count;
// loop {
// conversion_count = 0;
// let mut formula_replaced = formula.to_string();
// loop {
// let old_formula_replaced = formula_replaced.clone();
// rand_conversions.shuffle(&mut rng);
// for (short, long) in conversions {
// if formula_replaced.contains(long) {
// conversion_count += 1;
// }
// formula_replaced = replace_any(formula_replaced, long, short, &mut rng);
// }
// if formula_replaced == old_formula_replaced {
// break;
// }
// }
// if conversion_count < old_conversion_count {
// println!("{conversion_count}");
// old_conversion_count = conversion_count;
// }
// }
// }
// fn replace_any(input: String, long: &str, short: &str, rng: &mut ThreadRng) -> String {
// let haystack: Vec<_> = input.chars().collect();
// let needle: Vec<_> = long.chars().collect();
// let short_chars: Vec<_> = short.chars().collect();
// let length = needle.len();
// let mut count = 0;
// for i in 0..haystack.len() - length {
// if needle == haystack[i..i + length] {
// count += 1;
// }
// }
// let possible_indices: Vec<_> = (0..count).collect();
// let take_amount = possible_indices.choose(rng).unwrap();
// let idxs_chosen: Vec<_> = possible_indices
// .sample(rng, *take_amount)
// .copied()
// .collect();
// let mut result: Vec<char> = Vec::new();
// let mut idx = 0;
// let mut i = 0;
// loop {
// if needle == haystack[i..i + length] {
// if idxs_chosen.contains(&idx) {
// for (j, c) in short_chars.iter().enumerate() {
// result[j] = *c;
// }
// }
// idx += 1;
// } else {
// result.push(haystack[i]);
// }
// todo!()
// }
// haystack.into_iter().collect()
// }
fn part1(conversions: &[(&str, &str)], formula: &[String]) {
let mut results = HashSet::new();
for (i, chemical) in formula.iter().enumerate() {
for (old_molecule, new_molecule) in conversions {
if chemical.as_str() == *old_molecule {
let mut new_chemical = formula.to_vec();
new_chemical[i] = new_molecule.to_string();
let new_chemical_string = new_chemical
.into_iter()
.reduce(|mut a, b| {
a.push_str(&b);
a
})
.unwrap();
results.insert(new_chemical_string);
}
}
}
println!("{}", results.len());
}
// ------------------------------------------------------------------------------------
use std::fs;
/// A single replacement rule as it appears in the puzzle input: `from => to`.
struct Rule {
from: String,
to: String,
}
/// Parse the puzzle input into (rules, medicine molecule).
fn parse_input(input: &str) -> (Vec<Rule>, String) {
let mut rules = Vec::new();
let mut molecule = String::new();
for line in input.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some((from, to)) = line.split_once(" => ") {
rules.push(Rule {
from: from.to_string(),
to: to.to_string(),
});
} else {
// The one line with no " => " is the medicine molecule.
molecule = line.to_string();
}
}
(rules, molecule)
}
/// Very small xorshift-based PRNG so we don't need an external crate just to
/// shuffle the rule order between restart attempts.
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
// Avoid a zero seed, which would make xorshift degenerate.
Rng(seed | 1)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
/// Random index in [0, n).
fn gen_range(&mut self, n: usize) -> usize {
(self.next_u64() % n as u64) as usize
}
fn shuffle<T>(&mut self, slice: &mut [T]) {
// Fisher-Yates shuffle.
for i in (1..slice.len()).rev() {
let j = self.gen_range(i + 1);
slice.swap(i, j);
}
}
}
/// Try a single greedy-reduction pass: starting from `molecule`, repeatedly
/// replace the *first* substring match (scanning rules in the given order,
/// and within the string left-to-right) of some rule's `to` with its `from`,
/// counting each replacement as one step, until either:
/// - the molecule becomes exactly "e" (success), or
/// - no rule matches anywhere in the current molecule (stuck).
///
/// Returns `Some(steps)` on success, `None` if it got stuck.
fn try_greedy_reduce(molecule: &str, rules: &[&Rule]) -> Option<usize> {
let mut current = molecule.to_string();
let mut steps = 0usize;
while current != "e" {
let mut reduced_this_round = false;
for rule in rules {
if let Some(pos) = current.find(rule.to.as_str()) {
// Replace just the first occurrence found.
current.replace_range(pos..pos + rule.to.len(), &rule.from);
steps += 1;
reduced_this_round = true;
break;
}
}
if !reduced_this_round {
// No rule applies anywhere; this attempt is stuck.
return None;
}
}
Some(steps)
}
/// Repeatedly attempt greedy reduction with randomly reshuffled rule order
/// until one attempt successfully collapses the molecule down to "e".
///
/// This restart-on-stuck strategy works because, for these puzzle inputs,
/// *some* greedy left-to-right reduction order reaches "e" in the minimal
/// number of steps; we just need to find an order that doesn't get stuck.
fn solve_part2(rules: &[Rule], molecule: &str) -> usize {
let mut rule_refs: Vec<&Rule> = rules.iter().collect();
let mut rng = Rng::new(0x5EED_1234_u64);
loop {
if let Some(steps) = try_greedy_reduce(molecule, &rule_refs) {
return steps;
}
rng.shuffle(&mut rule_refs);
}
}
fn main2() {
let input = fs::read_to_string("input.txt").expect("failed to read input.txt");
let (rules, molecule) = parse_input(&input);
let steps = solve_part2(&rules, &molecule);
println!("Part 2: fewest steps to fabricate the medicine = {}", steps);
}
#[cfg(test)]
mod tests {
use super::*;
fn rules_from_pairs(pairs: &[(&str, &str)]) -> Vec<Rule> {
pairs
.iter()
.map(|(from, to)| Rule {
from: from.to_string(),
to: to.to_string(),
})
.collect()
}
#[test]
fn example_hoh_takes_3_steps() {
let rules = rules_from_pairs(&[
("e", "H"),
("e", "O"),
("H", "HO"),
("H", "OH"),
("O", "HH"),
]);
let steps = solve_part2(&rules, "HOH");
assert_eq!(steps, 3);
}
#[test]
fn example_hohoho_takes_6_steps() {
let rules = rules_from_pairs(&[
("e", "H"),
("e", "O"),
("H", "HO"),
("H", "OH"),
("O", "HH"),
]);
let steps = solve_part2(&rules, "HOHOHO");
assert_eq!(steps, 6);
}
#[test]
fn parse_input_splits_rules_and_molecule() {
let input = "e => H\ne => O\nH => HO\nH => OH\nO => HH\n\nHOH\n";
let (rules, molecule) = parse_input(input);
assert_eq!(rules.len(), 5);
assert_eq!(molecule, "HOH");
assert_eq!(rules[0].from, "e");
assert_eq!(rules[0].to, "H");
}
} }