91 lines
2.3 KiB
Rust
91 lines
2.3 KiB
Rust
#![feature(array_windows)]
|
|
#![feature(test)]
|
|
extern crate test;
|
|
use aoc2021::common::*;
|
|
use fnv::FnvHashMap;
|
|
|
|
const DAY: usize = 14;
|
|
type Parsed = (FnvHashMap<[u8; 2], u8>, String);
|
|
|
|
fn parse_input(raw: &str) -> Parsed {
|
|
let (state, rules) = raw.split_once("\n\n").unwrap();
|
|
let rules = rules.lines().map(|line| line.as_bytes()).map(|bytes| ([bytes[0], bytes[1]], bytes[bytes.len() - 1])).collect();
|
|
(rules, state.to_owned())
|
|
}
|
|
|
|
fn grow_polys((rules, raw_state): &Parsed, generations: usize) -> usize {
|
|
let mut state = FnvHashMap::default();
|
|
for polymer in raw_state.as_bytes().array_windows() {
|
|
*state.entry(*polymer).or_insert(0) += 1;
|
|
}
|
|
for _ in 0..generations {
|
|
for ([p1, p2], quantity) in state.clone() {
|
|
let output = rules[&[p1, p2]];
|
|
*state.entry([p1, output]).or_insert(0) += quantity;
|
|
*state.entry([output, p2]).or_insert(0) += quantity;
|
|
*state.get_mut(&[p1, p2]).unwrap() -= quantity;
|
|
}
|
|
}
|
|
let mut charcounts = FnvHashMap::default();
|
|
for (p, q) in state.into_iter().flat_map(|([p1, p2], q)| [(p1, q), (p2, q)]) {
|
|
*charcounts.entry(p).or_insert(0) += q;
|
|
}
|
|
for (&p, q) in charcounts.iter_mut() {
|
|
// This implementation counts each element except the very first and very last twice.
|
|
// We add 1 for those and then divide by 2.
|
|
*q += (p == raw_state.bytes().next().unwrap()) as usize;
|
|
*q += (p == raw_state.bytes().last().unwrap()) as usize;
|
|
*q /= 2;
|
|
}
|
|
charcounts.values().max().unwrap() - charcounts.values().filter(|&&q| q != 0).min().unwrap()
|
|
}
|
|
|
|
fn part1(parsed: &Parsed) -> usize {
|
|
grow_polys(parsed, 10)
|
|
}
|
|
|
|
fn part2(parsed: &Parsed) -> usize {
|
|
grow_polys(parsed, 40)
|
|
}
|
|
|
|
fn main() {
|
|
let input = parse_input(&read_file(DAY));
|
|
println!("Part 1: {}", part1(&input));
|
|
println!("Part 2: {}", part2(&input));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use aoc2021::*;
|
|
|
|
const TEST_INPUT: &str = "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";
|
|
|
|
test!(part1() == 1588);
|
|
test!(part2() == 2188189693529);
|
|
bench!(part1() == 3587);
|
|
bench!(part2() == 3906445077999);
|
|
bench_input!(input_size => 120);
|
|
|
|
fn input_size((m, s): &Parsed) -> usize {
|
|
m.len() + s.len()
|
|
}
|
|
}
|