use std::collections::HashSet; pub fn process_part1(input: &str) -> u32 { let (replacements_str, molecule) = input.split_once("\n\n").unwrap(); let mut replacements = Vec::new(); replacements_str.lines().for_each(|line| { let (from, to) = line.split_once(" => ").unwrap(); replacements.push((from, to)); }); let mut possible = HashSet::new(); for (from, to) in replacements { molecule.match_indices(from).for_each(|(idx, _)| { let mut new_possible = molecule.to_string(); new_possible.replace_range(idx..(idx + from.len()), to); possible.insert(new_possible); }); } possible.len() as u32 } // https://www.reddit.com/r/adventofcode/comments/3xflz8/comment/cy4etju/ // broken test //First insight // //There are only two types of productions: // // e => XX and X => XX (X is not Rn, Y, or Ar) // // X => X Rn X Ar | X Rn X Y X Ar | X Rn X Y X Y X Ar // //Second insight // //You can think of Rn Y Ar as the characters ( , ): // //X => X(X) | X(X,X) | X(X,X,X) // //Whenever there are two adjacent "elements" in your "molecule", you apply the first production. This reduces your molecule length by 1 each time. // //And whenever you have T(T) T(T,T) or T(T,T,T) (T is a literal token such as "Mg", i.e. not a nonterminal like "TiTiCaCa"), you apply the second production. This reduces your molecule length by 3, 5, or 7. //Third insight // //Repeatedly applying X => XX until you arrive at a single token takes count(tokens) - 1 steps: // //ABCDE => XCDE => XDE => XE => X //count("ABCDE") = 5 //5 - 1 = 4 steps // //Applying X => X(X) is similar to X => XX, except you get the () for free. This can be expressed as count(tokens) - count("(" or ")") - 1. // //A(B(C(D(E)))) => A(B(C(X))) => A(B(X)) => A(X) => X //count("A(B(C(D(E))))") = 13 //count("(((())))") = 8 //13 - 8 - 1 = 4 steps // //You can generalize to X => X(X,X) by noting that each , reduces the length by two (,X). The new formula is count(tokens) - count("(" or ")") - 2*count(",") - 1. // //A(B(C,D),E(F,G)) => A(B(C,D),X) => A(X,X) => X //count("A(B(C,D),E(F,G))") = 16 //count("(()())") = 6 //count(",,,") = 3 //16 - 6 - 2*3 - 1 = 3 steps // //This final formula works for all of the production types (for X => XX, the (,) counts are zero by definition.) pub fn process_part2(input: &str) -> u32 { let (_replacements_str, molecule) = input.split_once("\n\n").unwrap(); // An element is always at least an uppercase character and possibly an additional lowercase // character let total_elements = molecule.chars().filter(|char| char.is_uppercase()).count(); let num_rn = molecule.matches("Rn").count(); let num_ar = molecule.matches("Ar").count(); let num_y = molecule.matches("Y").count(); (total_elements - (num_rn + num_ar) - 2 * num_y - 1) as u32 } #[cfg(test)] mod tests { use super::*; const INPUT1: &str = "H => HO H => OH O => HH e => H e => O HOH"; const INPUT2: &str = "H => HO H => OH O => HH e => H e => O HOHOHO"; #[test] fn part1() { let result = process_part1(INPUT1); assert_eq!(result, 4); let result = process_part1(INPUT2); assert_eq!(result, 7); } #[test] fn part2() { let result = process_part2(INPUT1); assert_eq!(result, 3); let result = process_part2(INPUT2); assert_eq!(result, 6); } }