diff --git a/aoc2021/input/day14.txt b/aoc2021/input/day14.txt new file mode 100644 index 0000000..eed6b77 --- /dev/null +++ b/aoc2021/input/day14.txt @@ -0,0 +1,102 @@ +CKFFSCFSCBCKBPBCSPKP + +NS -> P +KV -> B +FV -> S +BB -> V +CF -> O +CK -> N +BC -> B +PV -> N +KO -> C +CO -> O +HP -> P +HO -> P +OV -> O +VO -> C +SP -> P +BV -> H +CB -> F +SF -> H +ON -> O +KK -> V +HC -> N +FH -> P +OO -> P +VC -> F +VP -> N +FO -> F +CP -> C +SV -> S +PF -> O +OF -> H +BN -> V +SC -> V +SB -> O +NC -> P +CN -> K +BP -> O +PC -> H +PS -> C +NB -> K +VB -> P +HS -> V +BO -> K +NV -> B +PK -> K +SN -> H +OB -> C +BK -> S +KH -> P +BS -> S +HV -> O +FN -> F +FS -> N +FP -> F +PO -> B +NP -> O +FF -> H +PN -> K +HF -> H +VK -> K +NF -> K +PP -> H +PH -> B +SK -> P +HN -> B +VS -> V +VN -> N +KB -> O +KC -> O +KP -> C +OS -> O +SO -> O +VH -> C +OK -> B +HH -> B +OC -> P +CV -> N +SH -> O +HK -> N +NO -> F +VF -> S +NN -> O +FK -> V +HB -> O +SS -> O +FB -> B +KS -> O +CC -> S +KF -> V +VV -> S +OP -> H +KN -> F +CS -> H +CH -> P +BF -> F +NH -> O +NK -> C +OH -> C +BH -> O +FC -> V +PB -> B diff --git a/aoc2021/input/day14_provided.txt b/aoc2021/input/day14_provided.txt new file mode 100644 index 0000000..b5594dd --- /dev/null +++ b/aoc2021/input/day14_provided.txt @@ -0,0 +1,18 @@ +NNCB + +CH -> B +HH -> N +CB -> H +NH -> C +HB -> C +HC -> B +HN -> C +NN -> C +BH -> H +NC -> B +NB -> B +BN -> B +BB -> N +BC -> B +CC -> N +CN -> C diff --git a/aoc2021/src/day14.rs b/aoc2021/src/day14.rs new file mode 100644 index 0000000..b94d370 --- /dev/null +++ b/aoc2021/src/day14.rs @@ -0,0 +1,160 @@ +use std::collections::{HashMap, HashSet, LinkedList}; +use std::fmt::Write; + +use anyhow::{Context, Result}; + +const INPUT: &str = include_str!("../input/day14.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + + Ok(res) +} + +fn part1(input: &str) -> Result { + let (polymer, rules) = input.split_once("\n\n").context("couldn't split input")?; + let mut polymer: Polymer = polymer.parse()?; + let rules: Rules = rules.parse()?; + + let mut molecule_set = polymer.molecule_set(); + rules.0.values().for_each(|v| { + molecule_set.insert(*v); + }); + + for _ in 0..10 { + polymer.insert_pairs(&rules.0)?; + } + + let occurrences = polymer.compute_occurrences(&molecule_set); + + let (_, least_common_occurrences) = occurrences + .iter() + .min_by_key(|(_, occurrences)| occurrences) + .unwrap(); + let (_, most_common_occurrences) = occurrences + .iter() + .max_by_key(|(_, occurrences)| occurrences) + .unwrap(); + + Ok(most_common_occurrences - least_common_occurrences) +} + +struct Rules(HashMap<(char, char), char>); + +impl std::str::FromStr for Rules { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(Self( + s.lines() + .map(str::trim) + .map(|l| { + let (pair, res) = l.split_once(" -> ").context("couldn't parse rule")?; + Ok(( + ( + pair.chars().next().context("")?, + pair.chars().nth(1).context("")?, + ), + res.chars().next().context("couldn't parse rule")?, + )) + }) + .collect::>()?, + )) + } +} + +#[derive(Debug)] +struct Polymer { + molecules: Option>, +} + +impl Polymer { + fn insert_pairs(&mut self, rules: &HashMap<(char, char), char>) -> Result<()> { + debug_assert!(self.molecules.is_some()); + + self.molecules = Some(insert_pairs( + std::mem::replace(&mut self.molecules, None).unwrap(), + rules, + )?); + Ok(()) + } + + fn compute_occurrences(&self, molecule_set: &HashSet) -> Vec<(char, usize)> { + debug_assert!(self.molecules.is_some()); + + let mut res = Vec::new(); + for molecule in molecule_set { + let count = self + .molecules + .as_ref() + .unwrap() // we always have a Some, Option only used for std::mem::replace + .iter() + .filter(|&m| m == molecule) + .count(); + res.push((*molecule, count)); + } + + res + } + + fn molecule_set(&self) -> HashSet { + debug_assert!(self.molecules.is_some()); + + self.molecules.as_ref().unwrap().iter().copied().collect() + } +} + +impl std::str::FromStr for Polymer { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let molecules = s.trim().chars().collect(); + + Ok(Polymer { + molecules: Some(molecules), + }) + } +} + +fn insert_pairs( + mut molecules: LinkedList, + rules: &HashMap<(char, char), char>, +) -> Result> { + if molecules.len() <= 1 { + return Ok(molecules); + } + + // List length is at least 2 + let mut iter = molecules.iter(); + let (left, right) = (*iter.next().unwrap(), *iter.next().unwrap()); + + let to_insert = *rules + .get(&(left, right)) + .with_context(|| format!("couldn't find rule for pair ({}, {})", left, right))?; + + let mut tail = insert_pairs(molecules.split_off(1), rules)?; + + molecules.push_back(to_insert); + molecules.append(&mut tail); + + Ok(molecules) +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED: &str = include_str!("../input/day14_provided.txt"); + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED).unwrap(), 1588); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 3247); + } +} diff --git a/aoc2021/src/lib.rs b/aoc2021/src/lib.rs index cc37626..4b6855a 100644 --- a/aoc2021/src/lib.rs +++ b/aoc2021/src/lib.rs @@ -13,3 +13,4 @@ pub mod day10; pub mod day11; pub mod day12; pub mod day13; +pub mod day14; diff --git a/aoc2021/src/main.rs b/aoc2021/src/main.rs index 1fa15e3..0c06f85 100644 --- a/aoc2021/src/main.rs +++ b/aoc2021/src/main.rs @@ -15,6 +15,7 @@ use aoc2021::day10; use aoc2021::day11; use aoc2021::day12; use aoc2021::day13; +use aoc2021::day14; fn main() -> Result<()> { let days: &[DayFunc] = &[ @@ -31,6 +32,7 @@ fn main() -> Result<()> { day11::run, day12::run, day13::run, + day14::run, ]; aoc::run(days)