diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f2a00..da5f11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Of note: - The changelog 2015.5.2 has been rewritten from each commit content. - This file may be amended entirely in the future to adhere to the [GNU Changelog style](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs) +## [2015.19.1] +### Added +- Solved [exercice for 2015, day 19](src/year_2015/day_19.rs). + ## [2015.18.1] ### Added - Solved [exercice for 2015, day 18](src/year_2015/day_18.rs). diff --git a/Cargo.toml b/Cargo.toml index d059165..10ae3aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-rs" -version = "2015.18.1" +version = "2015.19.1" edition = "2021" authors = ["Arnaud 'red' Rouyer"] readme = "README.md" diff --git a/NOTES_2015.md b/NOTES_2015.md index 1863bb0..e876ec1 100644 --- a/NOTES_2015.md +++ b/NOTES_2015.md @@ -533,3 +533,28 @@ year_2015::day_18/year_2015::day_18_v2 That was fun. My [distinguished competitor](https://docs.rs/advent-of-code/2022.0.66/src/advent_of_code/year2015/day18.rs.html) used a single-dimensional vector approach, which may have a better performance, but quite frankly, I prefer having representation closer to "physical" reality. But it's just me, don't listen to me too much. + +## Day 19: Medicine for Rudolph + +
+📊Tests and benchmarks + +``` +test year_2015::day_19::tests::works_with_samples_v1 ... ok +test year_2015::day_19::tests::works_with_samples_v2 ... ok +test year_2015_day_19 ... ok + +year_2015::day_19/year_2015::day_19_v1 + time: [565.05 µs 565.71 µs 566.40 µs] +year_2015::day_19/year_2015::day_19_v2 + time: [6.0105 µs 6.0169 µs 6.0237 µs] +``` +
+ +
+Ruby version comments + +> Part one is nothing to write home about. However, part two... First idea was the good ol' bruteforce approach, but when searching if there was an algorithm I didn't know, I stumbled upon a [very interesting Reddit comment](https://www.reddit.com/r/adventofcode/comments/3xflz8/day_19_solutions/cy4h7ji/)... +
+ +Okay, that was less painful than I remembered it, and my Rust code is actually clearer than my Ruby code. diff --git a/README.md b/README.md index 1d98a1f..b638184 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ As I said, [Consistency is hard](https://github.com/joshleaves/advent-rb), and I I'm also adding notes that may be useful if you're discovering Rust (like I am). Notes for solving: -* [2015, up to day 16](NOTES_2015.md) +* [2015, up to day 19](NOTES_2015.md) # Regarding style rules I'm gonna use a mix of what `cargo fmt` does, with some stuff that feels more natural to me. diff --git a/benches/advent-bench.rs b/benches/advent-bench.rs index 62280c3..16c9c8b 100644 --- a/benches/advent-bench.rs +++ b/benches/advent-bench.rs @@ -16,6 +16,7 @@ use advent_rs::year_2015::day_15; use advent_rs::year_2015::day_16; use advent_rs::year_2015::day_17; use advent_rs::year_2015::day_18; +use advent_rs::year_2015::day_19; use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn year_2015_benchmark(c: &mut Criterion) { @@ -200,6 +201,16 @@ pub fn year_2015_benchmark(c: &mut Criterion) { b.iter(|| day_18::day_18_v2(black_box(input_year_2015_day_18))) }); g2015_day_18.finish(); + + let mut g2015_day_19 = c.benchmark_group("year_2015::day_19"); + let input_year_2015_day_19 = include_str!("../inputs/year_2015_day_19_input"); + g2015_day_19.bench_function("year_2015::day_19_v1", |b| { + b.iter(|| day_19::day_19_v1(black_box(input_year_2015_day_19))) + }); + g2015_day_19.bench_function("year_2015::day_19_v2", |b| { + b.iter(|| day_19::day_19_v2(black_box(input_year_2015_day_19))) + }); + g2015_day_19.finish(); } criterion_group!(benches, year_2015_benchmark); diff --git a/inputs/year_2015_day_19_input b/inputs/year_2015_day_19_input new file mode 100644 index 0000000..2714445 --- /dev/null +++ b/inputs/year_2015_day_19_input @@ -0,0 +1,45 @@ +Al => ThF +Al => ThRnFAr +B => BCa +B => TiB +B => TiRnFAr +Ca => CaCa +Ca => PB +Ca => PRnFAr +Ca => SiRnFYFAr +Ca => SiRnMgAr +Ca => SiTh +F => CaF +F => PMg +F => SiAl +H => CRnAlAr +H => CRnFYFYFAr +H => CRnFYMgAr +H => CRnMgYFAr +H => HCa +H => NRnFYFAr +H => NRnMgAr +H => NTh +H => OB +H => ORnFAr +Mg => BF +Mg => TiMg +N => CRnFAr +N => HSi +O => CRnFYFAr +O => CRnMgAr +O => HP +O => NRnFAr +O => OTi +P => CaP +P => PTi +P => SiRnFAr +Si => CaSi +Th => ThCa +Ti => BP +Ti => TiTi +e => HF +e => NAl +e => OMg + +ORnPBPMgArCaCaCaSiThCaCaSiThCaCaPBSiRnFArRnFArCaCaSiThCaCaSiThCaCaCaCaCaCaSiRnFYFArSiRnMgArCaSiRnPTiTiBFYPBFArSiRnCaSiRnTiRnFArSiAlArPTiBPTiRnCaSiAlArCaPTiTiBPMgYFArPTiRnFArSiRnCaCaFArRnCaFArCaSiRnSiRnMgArFYCaSiRnMgArCaCaSiThPRnFArPBCaSiRnMgArCaCaSiThCaSiRnTiMgArFArSiThSiThCaCaSiRnMgArCaCaSiRnFArTiBPTiRnCaSiAlArCaPTiRnFArPBPBCaCaSiThCaPBSiThPRnFArSiThCaSiThCaSiThCaPTiBSiRnFYFArCaCaPRnFArPBCaCaPBSiRnTiRnFArCaPRnFArSiRnCaCaCaSiThCaRnCaFArYCaSiRnFArBCaCaCaSiThFArPBFArCaSiRnFArRnCaCaCaFArSiRnFArTiRnPMgArF \ No newline at end of file diff --git a/src/year_2015.rs b/src/year_2015.rs index 90ea839..517e282 100644 --- a/src/year_2015.rs +++ b/src/year_2015.rs @@ -16,6 +16,7 @@ pub mod day_15; pub mod day_16; pub mod day_17; pub mod day_18; +pub mod day_19; /// Returns the solution for a specified exercise and input. /// @@ -63,6 +64,7 @@ pub fn solve(day: u8, part: u8, input: impl Into) -> Option { 16 => return Some(format!("{}", day_16::day_16(part, input))), 17 => return Some(format!("{}", day_17::day_17(part, input))), 18 => return Some(format!("{}", day_18::day_18(part, input))), + 19 => return Some(format!("{}", day_19::day_19(part, input))), _ => return None, } } diff --git a/src/year_2015/day_19.rs b/src/year_2015/day_19.rs new file mode 100644 index 0000000..b8f64f8 --- /dev/null +++ b/src/year_2015/day_19.rs @@ -0,0 +1,131 @@ +use std::collections::{HashMap, HashSet}; + +fn parse_molecules(input: &str) -> (String, String) { + let parts: Vec<_> = input.split(" => ").collect(); + (parts[0].to_string(), parts[1].to_string()) +} + +fn parse_input(input: &str) -> (HashMap>, String) { + let mut molecules: HashMap> = HashMap::new(); + let mut starter: String = String::new(); + let mut reached_target = false; + for line in input.lines() { + if line.is_empty() { + reached_target = true; + continue; + } + if reached_target { + starter.push_str(line); + continue; + } + let (from, to) = parse_molecules(line); + molecules.entry(from).or_insert_with(Vec::new).push(to); + } + + (molecules, starter) +} + +fn do_permutations(starter: &str, input: &str, replacements: &Vec) -> HashSet { + // re_input = /(#{input})/ + // parts = starter.scan(re_input).length + // replacements.each do |replacement| + // 0.upto(parts - 1) do |idx| + // new_str = starter.gsub(re_input).each_with_index do |_part, i| + // idx == i ? replacement : input + // end + // @permutations.push(new_str) + // end + // end + let mut permutations: HashSet = HashSet::new(); + for (idx, _) in starter.match_indices(input) { + for replacement in replacements.iter() { + let new_permutation = format!( + "{}{}{}", + &starter[..idx], + replacement, + &starter[idx + input.len()..] + ); + permutations.insert(new_permutation); + } + } + + permutations +} + +fn calculate_permutations(molecules: &HashMap>, starter: &str) -> usize { + // @permutations ||= begin + // @permutations = [] + // @molecules.each do |input, replacements| + // do_permutations(input, replacements) + // end + // @permutations.sort.uniq + // end + let mut permutations: HashSet = HashSet::new(); + for (molecule, replacements) in molecules { + let new_permutations = do_permutations(&starter, &molecule, &replacements); + permutations.extend(new_permutations); + } + permutations.len() +} + +pub fn day_19_v1(input: impl Into) -> usize { + let (molecules, starter) = parse_input(&input.into()); + + calculate_permutations(&molecules, &starter) +} + +pub fn day_19_v2(input: impl Into) -> usize { + let (_molecules, starter) = parse_input(&input.into()); + + let mut count_az = 0; + let mut count_rn = 0; + let mut count_ar = 0; + let mut count_y = 0; + let letters: Vec<_> = starter.chars().collect(); + for (idx, chr) in letters.iter().enumerate() { + match chr { + 'A' => { + count_az += 1; + if letters[idx + 1] == 'r' { + count_ar += 1; + } + } + 'R' => { + count_az += 1; + if letters[idx + 1] == 'n' { + count_rn += 1; + } + } + 'Y' => { + count_az += 1; + count_y += 1; + } + _ => { + if *chr >= 'A' && *chr <= 'Z' { + count_az += 1; + } + } + } + } + + count_az - count_rn - count_ar - (2 * count_y) - 1 +} + +solvable!(day_19, day_19_v1, day_19_v2, usize); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn works_with_samples_v1() { + let sample_one = "H => HO\nH => OH\nO => HH\n\n\nHOH"; + assert_eq!(day_19_v1(sample_one), 4); + } + + #[test] + fn works_with_samples_v2() { + let sample_two = "e => H\ne => O\nH => HO\nH => OH\nO => HH\n\nHOHOHO;"; + assert_eq!(day_19_v2(sample_two), 5); + } +} diff --git a/tests/year_2015_test.rs b/tests/year_2015_test.rs index 40b9718..1e57d32 100644 --- a/tests/year_2015_test.rs +++ b/tests/year_2015_test.rs @@ -16,6 +16,7 @@ use advent_rs::year_2015::day_15; use advent_rs::year_2015::day_16; use advent_rs::year_2015::day_17; use advent_rs::year_2015::day_18; +use advent_rs::year_2015::day_19; #[test] fn year_2015_day_01() { @@ -141,3 +142,10 @@ fn year_2015_day_18() { assert_eq!(day_18::day_18_v1(input), 821); assert_eq!(day_18::day_18_v2(input), 886); } + +#[test] +fn year_2015_day_19() { + let input = include_str!("../inputs/year_2015_day_19_input"); + assert_eq!(day_19::day_19_v1(input), 576); + assert_eq!(day_19::day_19_v2(input), 207); +}