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);
+}