//! Day 14 of 2021. use crate::prelude::*; /// Type alias for a map of pairs. type PairMap = HashMap<(char, char), char>; /// Type alias for a map of pair counts. type PairCounts = HashMap<(char, char), isize>; /// Get the solution for day 14 of 2021. pub fn solution() -> Solution { Solution::new(Day::new(14, 2021), part_1, part_2) .with_expected(3411, 7477815755570_i64) } /// Parse the puzzle input into the template and map of pairs. fn parse(input: &str) -> Result<(String, PairMap)> { let mut lines = input.lines(); let template = lines .next() .ok_or_else(|| eyre!("Invalid input: {}", input))? .to_string(); let mut pairs = HashMap::new(); for line in lines.skip(1) { let (a, b, c) = line .replace(" -> ", "") .chars() .collect_tuple() .ok_or_else(|| eyre!("Invalid line: {}", line))?; pairs.insert((a, b), c); } Ok((template, pairs)) } /// Apply the counts to the pairs and return the new counts to add. fn apply(counts: &PairCounts, pairs: &PairMap) -> PairCounts { let mut to_add = PairCounts::new(); for (pair, count) in counts { if let Some(character) = pairs.get(pair) { *to_add.entry((pair.0, *character)).or_default() += count; *to_add.entry((*character, pair.1)).or_default() += count; } } to_add } /// Count the totals of the pair counts. fn count_totals(counts: &PairCounts) -> HashMap { let mut totals = HashMap::new(); for ((_, b), count) in counts { // If I count the first character in the pair the end result ends up being // off by one...? *totals.entry(*b).or_default() += count; } totals } /// Solve the puzzle for a given amount of steps. fn run(input: &str, steps: isize) -> Result { let (template, pairs) = parse(input)?; let mut counts = PairCounts::new(); for (a, b) in template.chars().tuple_windows() { *counts.entry((a, b)).or_default() += 1; } for _ in 0..steps { counts = apply(&counts, &pairs); } let totals = count_totals(&counts) .into_iter() .sorted_by(|(_, a), (_, b)| a.cmp(b)); let (_, min) = totals .clone() .next() .ok_or_else(|| eyre!("No minimum found"))?; let (_, max) = totals.last().ok_or_else(|| eyre!("No maximum found"))?; Ok((max - min).to_string()) } /// The logic to solve part one. fn part_1(input: &str) -> Result { run(input, 10) } /// The logic to solve part two. fn part_2(input: &str) -> Result { run(input, 40) }