From ae25038131d64d721cd2ca6924126953df85ee2a Mon Sep 17 00:00:00 2001 From: Patrick Stevens Date: Thu, 16 Dec 2021 17:55:21 +0000 Subject: [PATCH] Day 14 (#15) --- Cargo.lock | 57 ++++++----- Cargo.toml | 1 + day_14/Cargo.toml | 15 +++ day_14/benches/bench.rs | 19 ++++ day_14/day14.m | 30 ++++++ day_14/input.txt | 102 +++++++++++++++++++ day_14/src/lib.rs | 213 ++++++++++++++++++++++++++++++++++++++++ day_14/src/main.rs | 7 ++ 8 files changed, 422 insertions(+), 22 deletions(-) create mode 100644 day_14/Cargo.toml create mode 100644 day_14/benches/bench.rs create mode 100644 day_14/day14.m create mode 100644 day_14/input.txt create mode 100644 day_14/src/lib.rs create mode 100644 day_14/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index edf46da..bdf8e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", @@ -157,7 +157,7 @@ checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -206,6 +206,13 @@ dependencies = [ "criterion", ] +[[package]] +name = "day_14" +version = "0.1.0" +dependencies = [ + "criterion", +] + [[package]] name = "day_16" version = "0.1.0" @@ -292,9 +299,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -305,6 +312,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "js-sys" version = "0.3.55" @@ -322,9 +335,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "log" @@ -343,9 +356,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -405,9 +418,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" dependencies = [ "unicode-xid", ] @@ -478,9 +491,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -505,9 +518,9 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" [[package]] name = "serde" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" [[package]] name = "serde_cbor" @@ -521,9 +534,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" dependencies = [ "proc-macro2", "quote", @@ -532,20 +545,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "syn" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index de58a14..3fc27ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,6 @@ members = [ "day_11", "day_12", "day_13", + "day_14", "day_16", ] diff --git a/day_14/Cargo.toml b/day_14/Cargo.toml new file mode 100644 index 0000000..1e64678 --- /dev/null +++ b/day_14/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "day_14" +version = "0.1.0" +authors = ["Smaug123 "] +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 diff --git a/day_14/benches/bench.rs b/day_14/benches/bench.rs new file mode 100644 index 0000000..4b34165 --- /dev/null +++ b/day_14/benches/bench.rs @@ -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); diff --git a/day_14/day14.m b/day_14/day14.m new file mode 100644 index 0000000..fe5e58f --- /dev/null +++ b/day_14/day14.m @@ -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 +] diff --git a/day_14/input.txt b/day_14/input.txt new file mode 100644 index 0000000..50d9cac --- /dev/null +++ b/day_14/input.txt @@ -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 \ No newline at end of file diff --git a/day_14/src/lib.rs b/day_14/src/lib.rs new file mode 100644 index 0000000..88bc67e --- /dev/null +++ b/day_14/src/lib.rs @@ -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(mut iter: I, f: fn(T) -> U) -> Option<(T, T)> + where + I: Iterator, + 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 { + 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); + } +} diff --git a/day_14/src/main.rs b/day_14/src/main.rs new file mode 100644 index 0000000..6a95862 --- /dev/null +++ b/day_14/src/main.rs @@ -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)); +}