solved day 19 part 2 with claude
This commit is contained in:
Generated
+70
@@ -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"
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "*"
|
||||||
|
|||||||
+324
-1
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user