From 11d881ce2e85633a603b731275a34cbfb4ce670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Tue, 14 Dec 2021 11:30:59 +0100 Subject: [PATCH] implement day 14 --- src/day_14/mod.rs | 74 ++++++++++++++++++++++++++++++++++++++ src/day_14/model.rs | 84 +++++++++++++++++++++++++++++++++++++++++++ src/day_14/parsing.rs | 67 ++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 226 insertions(+) create mode 100644 src/day_14/mod.rs create mode 100644 src/day_14/model.rs create mode 100644 src/day_14/parsing.rs diff --git a/src/day_14/mod.rs b/src/day_14/mod.rs new file mode 100644 index 0000000..bfdd8bc --- /dev/null +++ b/src/day_14/mod.rs @@ -0,0 +1,74 @@ +mod model; +mod parsing; + +use aoc_runner_derive::{aoc, aoc_generator}; +use itertools::Itertools; +pub use model::{Polymer, Rule, Rules, Input}; +pub use parsing::parse_input as parse_input_tokens; +use yap::IntoTokens; + +#[aoc_generator(day14)] +pub fn parse_input(input: &str) -> Input { + parse_input_tokens(&mut input.into_tokens()).unwrap() +} + +#[aoc(day14, part1)] +pub fn part1(input: &Input) -> usize { + solve(input, 10) +} + +pub fn solve(Input { polymer, rules }: &Input, iterations: usize) -> usize { + let mut polymer = polymer.clone(); + for _ in 0..iterations { + polymer.apply(rules) + } + let counts: Vec<(char, usize)> = polymer.element_counts().iter().sorted_by(|a, b| Ord::cmp(&b.1, &a.1)).map(|(element, count)| (*element, *count)).collect(); + counts.first().unwrap().1 - counts.last().unwrap().1 +} + +#[aoc(day14, part2)] +pub fn part2(input: &Input) -> usize { + solve(input, 40) +} + +#[cfg(test)] +mod test { + + const EXAMPLE_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"; + const RESULT_PART_1: usize = 1588; + const RESULT_PART_2: usize = 2188189693529; + + #[test] + fn part1_example() { + let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_1); + } + + #[test] + fn part2_example() { + let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_2); + } + + #[test] + fn parse_example() { + let example = super::parse_input(EXAMPLE_INPUT); + } +} diff --git a/src/day_14/model.rs b/src/day_14/model.rs new file mode 100644 index 0000000..bed8b59 --- /dev/null +++ b/src/day_14/model.rs @@ -0,0 +1,84 @@ +use std::{collections::HashMap, str::FromStr}; + +use itertools::Itertools; + +#[derive(Clone)] +pub struct Polymer { + pub first: char, + pub last: char, + pub map: HashMap<(char, char), usize>, +} + +impl FromStr for Polymer { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + let mut map = HashMap::new(); + for neighbours in s.chars().tuple_windows::<(char, char)>() { + map.entry(neighbours).and_modify(|count| *count += 1).or_insert(1); + } + let first = s.chars().next().unwrap(); + let last = s.chars().last().unwrap(); + Ok(Self { first, last, map }) + + } +} + +#[derive(Copy, Clone)] +pub struct Rule { + pub neighbours: (char, char), + pub separator: char, +} + +pub struct Rules { + pub map: HashMap<(char, char), char>, +} + +impl TryFrom> for Rules { + type Error = &'static str; + + fn try_from(rule_vec: Vec) -> Result { + if rule_vec.is_empty() { + return Err("rule set can't be empty"); + } + let mut rules = Rules { map: Default::default() }; + for rule in rule_vec { + if rules.map.contains_key(&rule.neighbours) { + return Err("ambigous rule set") + } + rules.map.insert(rule.neighbours, rule.separator); + } + Ok(rules) + } +} + +impl Polymer { + pub fn apply(&mut self, rules: &Rules) { + let mut new_map = HashMap::new(); + + for (neighbours, count) in self.map.iter() { + if let Some(separator) = rules.map.get(&neighbours) { + new_map.entry((neighbours.0, *separator)).and_modify(|old_count| *old_count += count).or_insert(*count); + new_map.entry((*separator, neighbours.1)).and_modify(|old_count| *old_count += count).or_insert(*count); + } else { + new_map.entry(*neighbours).and_modify(|old_count| *old_count += count).or_insert(*count); + } + } + self.map = new_map + } + pub fn element_counts(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert(self.first, 1); + map.insert(self.last, 1); + for ((left, right), count) in self.map.iter() { + map.entry(*left).and_modify(|old_count| *old_count += count).or_insert(*count); + map.entry(*right).and_modify(|old_count| *old_count += count).or_insert(*count); + } + map.iter().map(|(element, count)| (*element, count / 2)).collect() + } +} + +pub struct Input { + pub polymer: Polymer, + pub rules: Rules, +} diff --git a/src/day_14/parsing.rs b/src/day_14/parsing.rs new file mode 100644 index 0000000..81df757 --- /dev/null +++ b/src/day_14/parsing.rs @@ -0,0 +1,67 @@ +use yap::Tokens; + +use crate::parsing::{newline, parse_n}; + +use super::{Input, Polymer, Rule, Rules}; + +pub fn parse_input(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + let polymer = parse_polymer(t); + let tag = newline(t) && newline(t); + let rules = parse_rules(t); + if let (Some(polymer), true, Some(rules)) = (polymer, tag, rules) { + Some(Input { polymer, rules }) + } else { + None + } + }) +} + +pub fn parse_polymer(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + let polymer: String = t.many(|t| parse_element(t)).collect(); + if polymer.len() >= 2 { + polymer.parse().ok() + } else { + None + } + }) +} + +pub fn parse_rule(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + let neighbours = parse_neighbours(t); + let tag = t.tokens(" -> ".chars()); + let separator = parse_element(t); + + if let (Some(neighbours), true, Some(separator)) = (neighbours, tag, separator) { + let neighbours = (neighbours[0], neighbours[1]); + Some(Rule { neighbours, separator }) + } else { + None + } + }) +} + +pub fn parse_neighbours(tokens: &mut impl Tokens) -> Option<[char; 2]> { + parse_n(tokens, |t| parse_element(t), |_| true) +} + +pub fn parse_element(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + if let Some(c) = t.next() { + if c.is_ascii_uppercase() { + return Some(c); + } + } + None + }) +} + +pub fn parse_rules(tokens: &mut impl Tokens) -> Option { + tokens.optional(|t| { + let rules: Vec = t.sep_by(|t| parse_rule(t), |t| newline(t)).collect(); + rules.try_into().ok() + }) +} + diff --git a/src/lib.rs b/src/lib.rs index 7bf9202..a6ba82c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,5 +15,6 @@ pub mod day_10; pub mod day_11; pub mod day_12; pub mod day_13; +pub mod day_14; aoc_lib! { year = 2021 }