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.
|
||||
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]]
|
||||
name = "day19"
|
||||
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"
|
||||
|
||||
[dependencies]
|
||||
rand = "*"
|
||||
|
||||
+324
-1
@@ -1,3 +1,326 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rand::{
|
||||
prelude::ThreadRng,
|
||||
seq::{IndexedRandom, SliceRandom},
|
||||
};
|
||||
|
||||
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