Compare commits
1 commit
726f629e30
...
11d881ce2e
Author | SHA1 | Date | |
---|---|---|---|
11d881ce2e |
74
src/day_14/mod.rs
Normal file
74
src/day_14/mod.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
84
src/day_14/model.rs
Normal file
84
src/day_14/model.rs
Normal file
|
@ -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<Self, Self::Err> {
|
||||
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<Vec<Rule>> for Rules {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(rule_vec: Vec<Rule>) -> Result<Self, Self::Error> {
|
||||
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<char, usize> {
|
||||
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,
|
||||
}
|
67
src/day_14/parsing.rs
Normal file
67
src/day_14/parsing.rs
Normal file
|
@ -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<Item = char>) -> Option<Input> {
|
||||
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<Item = char>) -> Option<Polymer> {
|
||||
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<Item = char>) -> Option<Rule> {
|
||||
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<Item = char>) -> Option<[char; 2]> {
|
||||
parse_n(tokens, |t| parse_element(t), |_| true)
|
||||
}
|
||||
|
||||
pub fn parse_element(tokens: &mut impl Tokens<Item = char>) -> Option<char> {
|
||||
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<Item = char>) -> Option<Rules> {
|
||||
tokens.optional(|t| {
|
||||
let rules: Vec<Rule> = t.sep_by(|t| parse_rule(t), |t| newline(t)).collect();
|
||||
rules.try_into().ok()
|
||||
})
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in a new issue