Added Solution for 2021 day 14
This commit is contained in:
parent
67a9e89e84
commit
5e1a756b34
5 changed files with 332 additions and 0 deletions
8
2021/day14_extended_polymerization/Cargo.toml
Normal file
8
2021/day14_extended_polymerization/Cargo.toml
Normal file
|
@ -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]
|
75
2021/day14_extended_polymerization/challenge.txt
Normal file
75
2021/day14_extended_polymerization/challenge.txt
Normal file
|
@ -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).
|
129
2021/day14_extended_polymerization/src/lib.rs
Normal file
129
2021/day14_extended_polymerization/src/lib.rs
Normal file
|
@ -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<ParseIntError> 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<Self, Self::Error> {
|
||||||
|
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::<Result<Vec<_>, _>>()?;
|
||||||
|
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<u8, usize> {
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
102
2021/day14_extended_polymerization/tests/challenge_input
Normal file
102
2021/day14_extended_polymerization/tests/challenge_input
Normal file
|
@ -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
|
18
2021/day14_extended_polymerization/tests/sample_input
Normal file
18
2021/day14_extended_polymerization/tests/sample_input
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue