From ef204db9fc2f5a118827a3a88c508aa462505f2d Mon Sep 17 00:00:00 2001 From: red Date: Sun, 3 Mar 2024 00:47:52 +0100 Subject: [PATCH] Year 2015: Day 25 --- Cargo.toml | 2 +- NOTES_2015.md | 39 ++++++++++++ README.md | 2 +- benches/advent-bench.rs | 8 +++ inputs/year_2015_day_25_input | 1 + src/year_2015.rs | 8 +++ src/year_2015/day_25.rs | 110 ++++++++++++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 inputs/year_2015_day_25_input create mode 100644 src/year_2015/day_25.rs diff --git a/Cargo.toml b/Cargo.toml index 82faf59..87d9db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-rs" -version = "2015.24.1" +version = "2015.25.1" edition = "2021" authors = ["Arnaud 'red' Rouyer"] readme = "README.md" diff --git a/NOTES_2015.md b/NOTES_2015.md index 382e215..28d8009 100644 --- a/NOTES_2015.md +++ b/NOTES_2015.md @@ -688,3 +688,42 @@ year_2015::day_24/year_2015::day_24_v2 Would you believe it? The `combinations(n)` method also exists in Rust! + +## Day 25: Let It Snow + +
+đź“ŠTests and benchmarks + +``` +test year_2015::day_25::tests::works_with_samples ... ok +test year_2015::tests::day_25 ... ok + +year_2015::day_25/year_2015::day_25 + time: [148.18 ns 148.33 ns 148.49 ns] +``` +
+ +
+Ruby version comments + +> You could solve this one by calculating each number one-by-one, cell-by-cell,... Or you could use [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation). +> +> If you're careful, you will quickly understand that your operation will at some point look like `STARTER * MULT % MODULO * MULT % MODULO etc...`, and that once you know how many cells you need to iterate through, you can just repeat the `* MULT % MODULO` part. +> +> Not being a math nerd at all (I've learned to talk to machines, not to numbers), the subject was completely lost on me, but here is some help: the modulo operation itself is very funny: once you apply it once, you don't need to repeat it, since it would just end up on the same number. So you can just repeat the multiply operation many times (isn't that a "power" operation?) and apply the modulo only once. +> +> How about this: +> ```ruby +> > 9999 * 18 % 7 * 18 % 7 * 18 % 7 * 18 % 7 * 18 % 7 * 18 % 7 * 18 % 7 * 18 % 7 +> => 6 +> > 9999 * 18 * 18 * 18 * 18 * 18 * 18 * 18 * 18 % 7 % 7 % 7 % 7 % 7 % 7 % 7 % 7 +> => 6 +> > 9999 * 18 * 18 * 18 * 18 * 18 * 18 * 18 * 18 % 7 +> => 6 +> > 9999 * 18**8 % 7 +> => 6 +> ``` +> Now, I got it, and so do you. Merry Xmas! +
+ +That one was very easy in Rust too, I just had to [pick up an algorithm online](https://rob.co.bb/posts/2019-02-10-modular-exponentiation-in-rust/) to properly perform modular exponentiation since it doesn't seem available in Rust. diff --git a/README.md b/README.md index 20e0d7f..1f1cb36 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ As I said, [Consistency is hard](https://github.com/joshleaves/advent-rb), and I haven't taken the time to pick up a new language in quite a while. I'm also adding notes that may be useful if you're (like me) discovering Rust: -- [2015, up to day 24](NOTES_2015.md) +- [2015, complete!](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 53031d4..d90f45f 100644 --- a/benches/advent-bench.rs +++ b/benches/advent-bench.rs @@ -22,6 +22,7 @@ use advent_rs::year_2015::day_21; use advent_rs::year_2015::day_22; use advent_rs::year_2015::day_23; use advent_rs::year_2015::day_24; +use advent_rs::year_2015::day_25; use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn year_2015_benchmark(c: &mut Criterion) { @@ -266,6 +267,13 @@ pub fn year_2015_benchmark(c: &mut Criterion) { b.iter(|| day_24::day_24_v2(black_box(input_year_2015_day_24))) }); g2015_day_24.finish(); + + let mut g2015_day_25 = c.benchmark_group("year_2015::day_25"); + let input_year_2015_day_25 = include_str!("../inputs/year_2015_day_25_input"); + g2015_day_25.bench_function("year_2015::day_25", |b| { + b.iter(|| day_25::day_25(black_box(input_year_2015_day_25))) + }); + g2015_day_25.finish(); } criterion_group!(benches, year_2015_benchmark); diff --git a/inputs/year_2015_day_25_input b/inputs/year_2015_day_25_input new file mode 100644 index 0000000..113a9fa --- /dev/null +++ b/inputs/year_2015_day_25_input @@ -0,0 +1 @@ +3029,2947 diff --git a/src/year_2015.rs b/src/year_2015.rs index bade567..9209e14 100644 --- a/src/year_2015.rs +++ b/src/year_2015.rs @@ -57,6 +57,7 @@ pub mod day_21; pub mod day_22; pub mod day_23; pub mod day_24; +pub mod day_25; /// Returns the solution for a specified exercise and input. /// @@ -110,6 +111,7 @@ pub fn solve(day: u8, part: u8, input: impl Into) -> Option { 22 => Some(format!("{}", day_22::day_22(part, input))), 23 => Some(format!("{}", day_23::day_23(part, input))), 24 => Some(format!("{}", day_24::day_24(part, input))), + 25 => Some(format!("{}", day_25::day_25(input))), _ => None, } } @@ -284,4 +286,10 @@ mod tests { assert_eq!(day_24::day_24_v1(input), 10_439_961_859); assert_eq!(day_24::day_24_v2(input), 72_050_269); } + + #[test] + fn day_25() { + let input = include_str!("../inputs/year_2015_day_25_input"); + assert_eq!(day_25::day_25(input), 19_980_801); + } } diff --git a/src/year_2015/day_25.rs b/src/year_2015/day_25.rs new file mode 100644 index 0000000..0347f6f --- /dev/null +++ b/src/year_2015/day_25.rs @@ -0,0 +1,110 @@ +//! Advent of Code 2015: Day 25: Let It Snow + +use itertools::Itertools; + +const STARTER: u64 = 20_151_125; +const MULT: u64 = 252_533; +const MODULO: u64 = 33_554_393; + +fn mod_exp(mut base: u64, mut exp: u64, modulus: u64) -> u64 { + if modulus == 1 { + return 0; + } + let mut result: u64 = 1; + base = base % modulus; + while exp > 0 { + if exp % 2 == 1 { + result = result * base % modulus; + } + exp = exp >> 1; + base = base * base % modulus + } + result +} + +fn find_iterations_count(col: u64, row: u64) -> u64 { + ((((col + row - 1) * (col + row)) / 2) - row) + 1 +} + +fn parse_input(input: &str) -> (u64, u64) { + input + .lines() + .next() + .unwrap() + .split(",") + .map(|p| p.parse::().unwrap()) + .collect_tuple() + .expect("Wrong input") +} + +pub fn day_25(input: impl Into) -> u64 { + let (column, row) = parse_input(&input.into()); + let itr = find_iterations_count(column, row) - 1; + let exp = mod_exp(MULT, itr, MODULO); + + (STARTER * exp) % MODULO +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn works_with_samples() { + let sample: [[u64; 6]; 6] = [ + [ + 20_151_125, + 18_749_137, + 17_289_845, + 30_943_339, + 10_071_777, + 33_511_524, + ], + [ + 31_916_031, + 21_629_792, + 16_929_656, + 7_726_640, + 15_514_188, + 4_041_754, + ], + [ + 16_080_970, + 8_057_251, + 1_601_130, + 7_981_243, + 11_661_866, + 16_474_243, + ], + [ + 24_592_653, + 32_451_966, + 21_345_942, + 9_380_097, + 10_600_672, + 31_527_494, + ], + [ + 77_061, + 17_552_253, + 28_094_349, + 6_899_651, + 9_250_759, + 31_663_883, + ], + [ + 33_071_741, + 6_796_745, + 25_397_450, + 24_659_492, + 1_534_922, + 27_995_004, + ], + ]; + for (i, numbers) in sample.iter().enumerate() { + for (j, number) in numbers.iter().enumerate() { + assert_eq!(day_25(format!("{},{}", j + 1, i + 1)), *number); + } + } + } +}