111 lines
3.3 KiB
Rust
111 lines
3.3 KiB
Rust
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);
|
|
}
|
|
}
|