mirror of
https://github.com/Smaug123/advent-of-code-2021
synced 2025-10-13 15:58:39 +00:00
Day 14 (#15)
This commit is contained in:
15
day_14/Cargo.toml
Normal file
15
day_14/Cargo.toml
Normal 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
19
day_14/benches/bench.rs
Normal 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
30
day_14/day14.m
Normal 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
102
day_14/input.txt
Normal 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
213
day_14/src/lib.rs
Normal 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
7
day_14/src/main.rs
Normal 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));
|
||||
}
|
Reference in New Issue
Block a user