#![warn(clippy::pedantic)] use nom::{ bytes::complete::{tag, take_till}, character::complete::{anychar, newline}, combinator::recognize, multi::many1, sequence::{pair, separated_pair, terminated}, }; use std::collections::BTreeMap; use std::io::{stdin, Read}; use std::ops::Add; type Input<'a> = &'a str; type IResult<'a, T> = nom::IResult, T>; type Rule = ((char, char), char); fn rule(i: Input) -> IResult { separated_pair(pair(anychar, anychar), tag(" -> "), anychar)(i) } fn parse_input(i: Input) -> IResult<(&str, Vec)> { separated_pair( terminated(recognize(take_till(|c| c == '\n')), newline), newline, many1(terminated(rule, newline)), )(i) } #[derive(Clone, Debug, PartialEq, Eq)] struct Counter { counts: BTreeMap, } impl Counter { pub fn new() -> Self { Self { counts: BTreeMap::new(), } } pub fn push(&mut self, el: T) { *self.counts.entry(el).or_insert(0) += 1; } pub fn most_common(&self) -> Option<(&T, usize)> { self.counts .iter() .map(|(el, count)| (el, *count)) .max_by_key(|&(_, count)| count) } pub fn least_common(&self) -> Option<(&T, usize)> { self.counts .iter() .map(|(el, count)| (el, *count)) .min_by_key(|&(_, count)| count) } } impl FromIterator for Counter { fn from_iter>(iter: I) -> Self { let mut counts = Self::new(); for el in iter { counts.push(el); } counts } } impl Add<&Counter> for Counter { type Output = Self; fn add(mut self, other: &Self) -> Self { for (el, count) in &other.counts { *self.counts.entry(*el).or_insert(0) += count; } self } } impl Add for Counter { type Output = Self; fn add(mut self, other: Self) -> Self { for (el, count) in other.counts { *self.counts.entry(el).or_insert(0) += count; } self } } #[derive(Clone, Debug)] struct LetterCounter { rules: BTreeMap<(char, char), char>, counts: BTreeMap<(char, char, usize), Counter>, } impl LetterCounter { pub fn new(rules: BTreeMap<(char, char), char>) -> Self { let mut counts = BTreeMap::new(); for &(left, right) in rules.keys() { counts.insert((left, right, 0), Counter::from_iter([left])); } Self { rules, counts } } pub fn get_counts_right_exclusive( &mut self, left: char, right: char, depth: usize, ) -> &Counter { #[allow(clippy::map_entry)] // lifetimes don't work out if !self.counts.contains_key(&(left, right, depth)) { let middle = self.rules[&(left, right)]; let counts_left = self .get_counts_right_exclusive(left, middle, depth - 1) .clone(); let counts_right = self.get_counts_right_exclusive(middle, right, depth - 1); let counts = counts_left + counts_right; self.counts.insert((left, right, depth), counts); } &self.counts[&(left, right, depth)] } } fn main() { let mut input = String::new(); stdin().lock().read_to_string(&mut input).unwrap(); let (input, rules) = parse_input(&input).unwrap().1; let rules: BTreeMap<_, _> = rules.into_iter().collect(); let chars: Vec<_> = input.chars().collect(); let mut counter = LetterCounter::new(rules); let mut run = |steps| { let mut totals = chars.windows(2).fold(Counter::new(), |counts, x| { counts + counter.get_counts_right_exclusive(x[0], x[1], steps) }); totals.push(*chars.last().unwrap()); println!( "{:?}", totals.most_common().unwrap().1 - totals.least_common().unwrap().1 ); }; run(10); run(40); }