diff --git a/2021/Cargo.toml b/2021/Cargo.toml index 1e77142..23a5cba 100644 --- a/2021/Cargo.toml +++ b/2021/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +fnv = "1.0.7" impl_ops = "0.1.1" itertools = "0.10" paste = "1.0" diff --git a/2021/inputs/day14 b/2021/inputs/day14 new file mode 100644 index 0000000..7c05136 --- /dev/null +++ b/2021/inputs/day14 @@ -0,0 +1,102 @@ +PBVHVOCOCFFNBCNCCBHK + +FV -> C +SS -> B +SC -> B +BP -> K +VP -> S +HK -> K +FS -> F +CC -> V +VB -> P +OP -> B +FO -> N +FH -> O +VK -> N +PV -> S +HV -> O +PF -> F +HH -> F +NK -> S +NC -> S +FC -> H +FK -> K +OO -> N +HP -> C +NN -> H +BB -> H +CN -> P +PS -> N +VF -> S +CB -> B +OH -> S +CF -> C +OK -> P +CV -> V +CS -> H +KN -> B +OV -> S +HB -> C +OS -> V +PC -> B +CK -> S +PP -> K +SN -> O +VV -> C +NS -> F +PN -> K +HS -> P +VO -> B +VC -> B +NV -> P +VS -> N +FP -> F +HO -> S +KS -> O +BN -> F +VN -> P +OC -> K +SF -> P +PO -> P +SB -> O +FN -> F +OF -> F +CP -> C +HC -> O +PH -> O +BC -> O +NO -> C +BH -> C +VH -> S +KK -> O +SV -> K +KB -> K +BS -> S +HF -> B +NH -> S +PB -> N +HN -> K +SK -> B +FB -> F +KV -> S +BF -> S +ON -> S +BV -> P +KC -> S +NB -> S +NP -> B +BK -> K +NF -> C +BO -> K +KF -> B +KH -> N +SP -> O +CO -> S +KO -> V +SO -> B +CH -> C +KP -> C +FF -> K +PK -> F +OB -> H +SH -> C diff --git a/2021/src/bin/day14.rs b/2021/src/bin/day14.rs new file mode 100644 index 0000000..c66250f --- /dev/null +++ b/2021/src/bin/day14.rs @@ -0,0 +1,90 @@ +#![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() + } +}