From 5e1a756b347a3bdfa7b506c02044a9f58727deb4 Mon Sep 17 00:00:00 2001 From: Burnus Date: Tue, 25 Apr 2023 16:17:56 +0200 Subject: [PATCH] Added Solution for 2021 day 14 --- 2021/day14_extended_polymerization/Cargo.toml | 8 ++ .../challenge.txt | 75 ++++++++++ 2021/day14_extended_polymerization/src/lib.rs | 129 ++++++++++++++++++ .../tests/challenge_input | 102 ++++++++++++++ .../tests/sample_input | 18 +++ 5 files changed, 332 insertions(+) create mode 100644 2021/day14_extended_polymerization/Cargo.toml create mode 100644 2021/day14_extended_polymerization/challenge.txt create mode 100644 2021/day14_extended_polymerization/src/lib.rs create mode 100644 2021/day14_extended_polymerization/tests/challenge_input create mode 100644 2021/day14_extended_polymerization/tests/sample_input diff --git a/2021/day14_extended_polymerization/Cargo.toml b/2021/day14_extended_polymerization/Cargo.toml new file mode 100644 index 0000000..b082802 --- /dev/null +++ b/2021/day14_extended_polymerization/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day14_extended_polymerization" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/2021/day14_extended_polymerization/challenge.txt b/2021/day14_extended_polymerization/challenge.txt new file mode 100644 index 0000000..24a0934 --- /dev/null +++ b/2021/day14_extended_polymerization/challenge.txt @@ -0,0 +1,75 @@ +The incredible pressures at this depth are starting to put a strain on your submarine. The submarine has [polymerization](https://en.wikipedia.org/wiki/Polymerization) equipment that would produce suitable materials to reinforce the submarine, and the nearby volcanically-active caves should even have the necessary input elements in sufficient quantities. + +The submarine manual contains instructions for finding the optimal polymer formula; specifically, it offers a *polymer template* and a list of *pair insertion* rules (your puzzle input). You just need to work out what polymer would result after repeating the pair insertion process a few times. + +For example: + +``` +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 + +``` + +The first line is the *polymer template* - this is the starting point of the process. + +The following section defines the *pair insertion* rules. A rule like `AB -> C` means that when elements `A` and `B` are immediately adjacent, element `C` should be inserted between them. These insertions all happen simultaneously. + +So, starting with the polymer template `NNCB`, the first step simultaneously considers all three pairs: + +* The first pair (`NN`) matches the rule `NN -> C`, so element `*C*` is inserted between the first `N` and the second `N`. +* The second pair (`NC`) matches the rule `NC -> B`, so element `*B*` is inserted between the `N` and the `C`. +* The third pair (`CB`) matches the rule `CB -> H`, so element `*H*` is inserted between the `C` and the `B`. + +Note that these pairs overlap: the second element of one pair is the first element of the next pair. Also, because all pairs are considered simultaneously, inserted elements are not considered to be part of a pair until the next step. + +After the first step of this process, the polymer becomes `N*C*N*B*C*H*B`. + +Here are the results of a few steps using the above rules: + +``` +Template: NNCB +After step 1: NCNBCHB +After step 2: NBCCNBBBCBHCB +After step 3: NBBBCNCCNBBNBNBBCHBHHBCHB +After step 4: NBBNBNBBCCNBCNCCNBBNBBNBBBNBBNBBCBHCBHHNHCBBCBHCB + +``` + +This polymer grows quickly. After step 5, it has length 97; After step 10, it has length 3073. After step 10, `B` occurs 1749 times, `C` occurs 298 times, `H` occurs 161 times, and `N` occurs 865 times; taking the quantity of the most common element (`B`, 1749) and subtracting the quantity of the least common element (`H`, 161) produces `1749 - 161 = *1588*`. + +Apply 10 steps of pair insertion to the polymer template and find the most and least common elements in the result. *What do you get if you take the quantity of the most common element and subtract the quantity of the least common element?* + +Your puzzle answer was `2408`. + +\--- Part Two --- +---------- + +The resulting polymer isn't nearly strong enough to reinforce the submarine. You'll need to run more steps of the pair insertion process; a total of *40 steps* should do it. + +In the above example, the most common element is `B` (occurring `2192039569602` times) and the least common element is `H` (occurring `3849876073` times); subtracting these produces `*2188189693529*`. + +Apply *40* steps of pair insertion to the polymer template and find the most and least common elements in the result. *What do you get if you take the quantity of the most common element and subtract the quantity of the least common element?* + +Your puzzle answer was `2651311098752`. + +Both parts of this puzzle are complete! They provide two gold stars: \*\* + +At this point, you should [return to your Advent calendar](/2021) and try another puzzle. + +If you still want to see it, you can [get your puzzle input](14/input). \ No newline at end of file diff --git a/2021/day14_extended_polymerization/src/lib.rs b/2021/day14_extended_polymerization/src/lib.rs new file mode 100644 index 0000000..9e85e00 --- /dev/null +++ b/2021/day14_extended_polymerization/src/lib.rs @@ -0,0 +1,129 @@ +use core::fmt::Display; +use std::{num::ParseIntError, collections::HashMap}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + InvalidInput(String), + LineMalformed(String), + ParseIntError(std::num::ParseIntError), +} + +impl From for ParseError { + fn from(value: ParseIntError) -> Self { + Self::ParseIntError(value) + } +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidInput(v) => write!(f, "Input is invalid: {v}"), + Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"), + Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"), + } + } +} + +struct Rule { + left: u8, + right: u8, + insert: u8, +} + +impl TryFrom<&str> for Rule { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + if let Some((condition, insert)) = value.split_once(" -> ").map(|(c, i)| (c.as_bytes(), i.as_bytes())) { + if condition.len() == 2 && insert.len() == 1 { + Ok(Self { + left: condition[0], + right: condition[1], + insert: insert[0], + }) + } else { + Err(Self::Error::LineMalformed(value.to_string())) + } + } else { + Err(Self::Error::LineMalformed(value.to_string())) + } + } +} + +pub fn run(input: &str) -> Result<(usize, usize), ParseError> { + if let Some((template, rules)) = input.split_once("\n\n") { + + // We don't actually care about the order of elements, except for their immediate + // neighbours. So we are fine splitting the polymer into windows of size 2 and just + // tallying up how often a given pairing occurs. This speeds things up significantly, once + // the polymer grows large and the pairings occur multiple times. + let mut polymer = HashMap::new(); + template.as_bytes().windows(2).for_each(|w| { + polymer.entry((w[0], w[1])).and_modify(|count| *count += 1).or_insert(1); + }); + + let rules: Vec<_> = rules.lines().map(Rule::try_from).collect::, _>>()?; + for _ in 0..10 { + polymerize(&mut polymer, &rules); + } + let elements = count_elements(&polymer); + let first = elements.values().max().unwrap_or(&0) - elements.values().min().unwrap_or(&0); + + for _ in 10..40 { + polymerize(&mut polymer, &rules); + } + let elements = count_elements(&polymer); + let second = elements.values().max().unwrap_or(&0) - elements.values().min().unwrap_or(&0); + Ok((first, second)) + } else { + Err(ParseError::InvalidInput("Unable to split into template and rules".to_string())) + } +} + +fn polymerize(polymer: &mut HashMap<(u8, u8), usize>, rules: &[Rule]) { + let mut new = HashMap::new(); + polymer.iter().for_each(|(&(lhs, rhs), &pair_count)| { + let insert = rules.iter().find(|r| r.left == lhs && r.right == rhs).map(|r| r.insert).unwrap(); + new.entry((lhs, insert)).and_modify(|count| *count += pair_count).or_insert(pair_count); + new.entry((insert, rhs)).and_modify(|count| *count += pair_count).or_insert(pair_count); + }); + std::mem::swap(&mut new, polymer); +} + +fn count_elements(polymer: &HashMap<(u8, u8), usize>) -> HashMap { + let mut counts = HashMap::new(); + + polymer.iter().for_each(|(&(lhs, rhs), &pair_count)| { + counts.entry(lhs).and_modify(|count| *count += pair_count).or_insert(pair_count); + counts.entry(rhs).and_modify(|count| *count += pair_count).or_insert(pair_count); + }); + + // We have counted every element twice so far, except for the very first and last one, which + // have been counted twice minus one (because they were lhs or rhs once less than if they'd + // been in the middle). Divide by 2, rounding up, to accomodate for that. + counts.iter_mut().for_each(|(_elem, count)| *count = (*count+1) / 2); + + counts +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; + + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + assert_eq!(run(&sample_input), Ok((1588, 2188189693529))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((2408, 2651311098752))); + } +} diff --git a/2021/day14_extended_polymerization/tests/challenge_input b/2021/day14_extended_polymerization/tests/challenge_input new file mode 100644 index 0000000..731174c --- /dev/null +++ b/2021/day14_extended_polymerization/tests/challenge_input @@ -0,0 +1,102 @@ +KHSNHFKVVSVPSCVHBHNP + +FV -> H +SB -> P +NV -> S +BS -> K +KB -> V +HB -> H +NB -> N +VB -> P +CN -> C +CF -> N +OF -> P +FO -> K +OC -> F +BN -> V +PO -> O +OS -> B +KH -> N +BB -> C +PV -> K +ON -> K +NF -> H +BV -> K +SN -> N +PB -> S +PK -> F +PF -> S +BP -> K +SP -> K +NN -> K +FP -> N +NK -> N +SF -> P +HS -> C +OH -> C +FS -> H +VH -> N +CO -> P +VP -> H +FF -> N +KP -> B +BH -> B +PP -> F +SS -> P +CV -> S +HO -> P +PN -> K +SO -> O +NO -> O +NH -> V +HH -> F +KK -> C +VO -> B +KS -> B +SV -> O +OP -> S +VK -> H +KF -> O +CP -> H +SH -> H +NC -> S +KC -> O +CK -> H +CH -> B +KO -> O +OV -> P +VF -> V +HN -> P +FH -> P +BC -> V +HV -> N +BO -> V +PH -> P +NP -> F +FN -> F +FK -> P +SC -> C +KN -> S +NS -> S +OK -> S +HK -> O +PC -> O +BK -> O +OO -> P +BF -> N +SK -> V +VS -> B +HP -> H +VC -> V +KV -> P +FC -> H +HC -> O +HF -> S +CB -> H +CC -> B +PS -> C +OB -> B +CS -> S +VV -> S +VN -> H +FB -> N diff --git a/2021/day14_extended_polymerization/tests/sample_input b/2021/day14_extended_polymerization/tests/sample_input new file mode 100644 index 0000000..b5594dd --- /dev/null +++ b/2021/day14_extended_polymerization/tests/sample_input @@ -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