This commit is contained in:
Patrick Stevens
2021-12-16 17:55:21 +00:00
committed by GitHub
parent b4ed32b7f7
commit ae25038131
8 changed files with 422 additions and 22 deletions

15
day_14/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "day_14"
version = "0.1.0"
authors = ["Smaug123 <patrick+github@patrickstevens.co.uk>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "bench"
harness = false

19
day_14/benches/bench.rs Normal file
View File

@@ -0,0 +1,19 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use day_14::day_14::{input, part_1, part_2};
fn criterion_benchmark(c: &mut Criterion) {
let input = input();
c.bench_function("day 14 part 1", |b| {
b.iter(|| {
black_box(part_1(&input));
})
});
c.bench_function("day 14 part 2", |b| {
b.iter(|| {
black_box(part_2(&input));
})
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

30
day_14/day14.m Normal file
View File

@@ -0,0 +1,30 @@
(* ::Package:: *)
parse[s_]:=With[{lines=StringSplit[s,"\n"]},
{lines[[1]],With[{sides=StringSplit[#," -> "]},Characters[First@sides]->{{StringTake[First@sides,{1}],Last@sides},{Last@sides,StringTake[First@sides,{2}]}}]&/@lines[[3;;]]}]
{start,rules}=parse@StringTrim@ReadString[StringJoin[NotebookDirectory[], "/input.txt"]];
(* ::Text:: *)
(*Part 1*)
step[rules_,chars_]:=Flatten[chars/.rules,1]
With[{l=SortBy[Tally[Join[StringTake[start,{{1},{-1}}],Flatten@Nest[step[rules,#]&,Partition[Characters@start,2,1],10]]],Last]},
Quotient[l[[-1]],2]-Quotient[l[[1]],2]
][[2]]
(* ::Text:: *)
(*Part 2*)
step[rules_,pairs_]:={#[[1,1]],Total[Last/@#]}&/@GatherBy[Flatten[Thread/@(pairs/.rules),1],First]
With[{l=Cases[Total@Flatten[{Times@@@Nest[step[rules,#]&,Tally@Partition[Characters@start,2,1],40],StringTake[start,{{1},{-1}}]}],_Integer,All]},
(Max[l]-Min[l])/2
]

102
day_14/input.txt Normal file
View File

@@ -0,0 +1,102 @@
SHHNCOPHONHFBVNKCFFC
HH -> K
PS -> P
BV -> H
HB -> H
CK -> F
FN -> B
PV -> S
KK -> F
OF -> C
SF -> B
KB -> S
HO -> O
NH -> N
ON -> V
VF -> K
VP -> K
PH -> K
FF -> V
OV -> N
BO -> K
PO -> S
CH -> N
FO -> V
FB -> H
FV -> N
FK -> S
VC -> V
CP -> K
CO -> K
SV -> N
PP -> B
BS -> P
VS -> C
HV -> H
NN -> F
NK -> C
PC -> V
HS -> S
FS -> S
OB -> S
VV -> N
VO -> P
KV -> F
SK -> O
BC -> P
BP -> F
NS -> P
SN -> S
NC -> N
FC -> V
CN -> S
OK -> B
SC -> N
HN -> B
HP -> B
KP -> B
CB -> K
KF -> C
OS -> B
BH -> O
PN -> K
VN -> O
KH -> F
BF -> H
HF -> K
HC -> P
NP -> H
KO -> K
CF -> H
BK -> O
OH -> P
SO -> F
BB -> F
VB -> K
SP -> O
SH -> O
PK -> O
HK -> P
CC -> V
NB -> O
NV -> F
OO -> F
VK -> V
FH -> H
SS -> C
NO -> P
CS -> H
BN -> V
FP -> N
OP -> N
PB -> P
OC -> O
SB -> V
VH -> O
KS -> B
PF -> N
KN -> H
NF -> N
CV -> K
KC -> B

213
day_14/src/lib.rs Normal file
View File

@@ -0,0 +1,213 @@
pub mod day_14 {
use std::collections::HashMap;
pub struct Data {
start: char,
end: char,
rules: HashMap<(char, char), char>,
}
pub(crate) fn parse(s: &str) -> (Data, HashMap<(char, char), u64>) {
let mut lines = s.split('\n');
let start = lines.next().unwrap();
match lines.next().unwrap() {
"" => {}
s => panic!("Expected empty line, got {}", s),
}
let mut map = HashMap::new();
for line in lines {
let mut iter = line.split(' ');
let pair = iter.next().unwrap();
match iter.next().unwrap() {
"->" => {}
s => panic!("Expected arrow, got {}", s),
}
let middle = iter.next().unwrap();
match iter.next() {
None => {}
Some(next) => panic!("Expected end of line, got {}", next),
}
let mut chars = pair.chars();
let p1 = chars.next().unwrap();
let p2 = chars.next().unwrap();
match chars.next() {
None => {}
Some(_) => panic!("Expected pair of chars, got {}", pair),
}
let mut chars = middle.chars();
let insert = chars.next().unwrap();
match chars.next() {
None => {}
Some(_) => panic!("Expected single char, got {}", middle),
}
map.insert((p1, p2), insert);
}
let mut pairs = HashMap::new();
let mut iter = start.chars();
let start = iter.next().unwrap();
let mut prev = start;
for next in iter {
pairs
.entry((prev, next))
.and_modify(|x| *x += 1)
.or_insert(1);
prev = next;
}
(
Data {
start,
end: prev,
rules: map,
},
pairs,
)
}
pub fn input() -> (Data, HashMap<(char, char), u64>) {
parse(include_str!("../input.txt"))
}
fn run(
data: &Data,
pairs: &HashMap<(char, char), u64>,
steps: u8,
) -> HashMap<(char, char), u64> {
let mut pairs = pairs.clone();
for _ in 0..steps {
let mut after_step = HashMap::new();
for (&(c1, c2), &count) in pairs.iter() {
match data.rules.get(&(c1, c2)) {
None => {
after_step
.entry((c1, c2))
.and_modify(|x| *x += count)
.or_insert(count);
}
Some(&middle) => {
after_step
.entry((c1, middle))
.and_modify(|x| *x += count)
.or_insert(count);
after_step
.entry((middle, c2))
.and_modify(|x| *x += count)
.or_insert(count);
}
};
}
pairs = after_step;
}
pairs
}
fn min_max<I, T, U>(mut iter: I, f: fn(T) -> U) -> Option<(T, T)>
where
I: Iterator<Item = T>,
T: Copy,
U: Ord + Copy,
{
let mut min_entry = iter.next()?;
let mut max_entry = min_entry;
let mut min = f(min_entry);
let mut max = min;
for entry in iter {
let f_val = f(entry);
if f_val < min {
min = f_val;
min_entry = entry;
} else if f_val > max {
max = f_val;
max_entry = entry;
}
}
Some((min_entry, max_entry))
}
fn count(map: &HashMap<(char, char), u64>, start: char, end: char) -> HashMap<char, u64> {
let mut counts = HashMap::new();
for (&(c1, c2), &count) in map.iter() {
counts
.entry(c1)
.and_modify(|i| *i += count)
.or_insert(count);
counts
.entry(c2)
.and_modify(|i| *i += count)
.or_insert(count);
}
*counts.get_mut(&start).unwrap() += 1;
*counts.get_mut(&end).unwrap() += 1;
counts
}
pub fn part_1(data: &(Data, HashMap<(char, char), u64>)) -> u64 {
let map = run(&data.0, &data.1, 10);
let counts = count(&map, data.0.start, data.0.end);
match min_max(counts.iter(), |x| x.1) {
Some((min, max)) => max.1 / 2 - min.1 / 2,
None => panic!("Expected a nonempty map"),
}
}
pub fn part_2(data: &(Data, HashMap<(char, char), u64>)) -> u64 {
let map = run(&data.0, &data.1, 40);
let counts = count(&map, data.0.start, data.0.end);
match min_max(counts.iter(), |x| x.1) {
Some((min, max)) => max.1 / 2 - min.1 / 2,
None => panic!("Expected a nonempty map"),
}
}
}
#[cfg(test)]
mod tests {
use super::day_14::*;
static 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]
fn part1_known() {
let data = parse(TEST_INPUT);
assert_eq!(part_1(&data), 1588);
}
#[test]
fn part2_known() {
let data = parse(TEST_INPUT);
assert_eq!(part_2(&data), 2188189693529);
}
#[test]
fn test_day_14() {
let input = input();
assert_eq!(part_1(&input), 2549);
assert_eq!(part_2(&input), 2516901104210);
}
}

7
day_14/src/main.rs Normal file
View File

@@ -0,0 +1,7 @@
use day_14::day_14::{input, part_1, part_2};
fn main() {
let input = input();
println!("part 1 => {}", part_1(&input));
println!("part 2 => {}", part_2(&input));
}