From 123bc36a4bd64775a2dd523ffba6af9795aa0ac4 Mon Sep 17 00:00:00 2001 From: red Date: Thu, 28 Mar 2024 04:07:53 +0100 Subject: [PATCH] Year 2017: Day 13 --- CHANGELOG.md | 10 ++++- Cargo.toml | 5 ++- NOTES_2017.md | 4 ++ README.md | 2 +- benches/year_2017.rs | 19 ++++++++- benches/year_2017_day_13.rs | 60 ++++++++++++++++++++++++++ inputs/year_2017/day_13_input | 43 +++++++++++++++++++ src/year_2017.rs | 9 ++++ src/year_2017/day_13.rs | 80 +++++++++++++++++++++++++++++++++++ 9 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 benches/year_2017_day_13.rs create mode 100644 inputs/year_2017/day_13_input create mode 100644 src/year_2017/day_13.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee322b..0fcbf1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ 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) +## [2017.13.1] +### Added +- Solved [exercice for 2017, day 13](src/year_2017/13.rs). +### Changed +- Added a lot of benchmarks and optimizations. + ## [2017.12.1] ### Added - Solved [exercice for 2017, day 08](src/year_2017/08.rs). @@ -30,9 +36,9 @@ Of note: - Solved [exercice for 2017, day 03](src/year_2017/03.rs). - Solved [exercice for 2017, day 04](src/year_2017/04.rs). - Solved [exercice for 2017, day 05](src/year_2017/05.rs). -### Changes +### Changed - Benchmarks (at least for 2017) now use `assert_eq!` to ensure that changes I do to optimize runtime don't end up breaking tests. -- More tests, more optimisations,... +- More tests, more optimizations,... ## [2016.25.2] ### Changed diff --git a/Cargo.toml b/Cargo.toml index 4a63593..9ebdb9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-rs" -version = "2017.12.1" +version = "2017.13.1" edition = "2021" authors = ["Arnaud 'red' Rouyer"] readme = "README.md" @@ -72,3 +72,6 @@ harness = false # [[bench]] # name = "year_2017_day_04" # harness = false +# [[bench]] +# name = "year_2017_day_13" +# harness = false diff --git a/NOTES_2017.md b/NOTES_2017.md index 0fa3abd..4fc7f03 100644 --- a/NOTES_2017.md +++ b/NOTES_2017.md @@ -51,3 +51,7 @@ Navigating hexagons is a pain. Naive approach is to start with "only change the ## Day 12: Digital Plumber I hate traversing trees. But you don't always have to. + +## Day 13: Packet Scanners + +The maths were a bit annoying on this one, and I was hoping for a specific mathematical formula that would prevent me from looping over all scenarios. I found something that felt promising and ported the code from Python, but to no avail, my original code was already faster... diff --git a/README.md b/README.md index e02f281..4183e26 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 (like me) discovering Rust: - [2015, complete!](NOTES_2015.md) - [2016, complete!](NOTES_2016.md) -- [2017, up to day 12](NOTES_2017.md) +- [2017, up to day 13](NOTES_2017.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/year_2017.rs b/benches/year_2017.rs index d1f1064..d0f93cd 100644 --- a/benches/year_2017.rs +++ b/benches/year_2017.rs @@ -10,6 +10,7 @@ use advent_rs::year_2017::day_09; use advent_rs::year_2017::day_10; use advent_rs::year_2017::day_11; use advent_rs::year_2017::day_12; +use advent_rs::year_2017::day_13; use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn year_2017_day_01(c: &mut Criterion) { @@ -195,6 +196,21 @@ fn year_2017_day_12(c: &mut Criterion) { g2017_day_12.finish(); } +fn year_2017_day_13(c: &mut Criterion) { + let input_day_13 = include_str!("../inputs/year_2017/day_13_input"); + assert_eq!(day_13::day_13_v1(black_box(input_day_13)), 2_264); + assert_eq!(day_13::day_13_v2(black_box(input_day_13)), 3_875_838); + + let mut g2017_day_13 = c.benchmark_group("year_2017::day_13"); + g2017_day_13.bench_function("year_2017::day_13_v1", |b| { + b.iter(|| day_13::day_13_v1(black_box(input_day_13))) + }); + g2017_day_13.bench_function("year_2017::day_13_v2", |b| { + b.iter(|| day_13::day_13_v2(black_box(input_day_13))) + }); + g2017_day_13.finish(); +} + criterion_group!( benches, year_2017_day_01, @@ -208,6 +224,7 @@ criterion_group!( year_2017_day_09, year_2017_day_10, year_2017_day_11, - year_2017_day_12 + year_2017_day_12, + year_2017_day_13 ); criterion_main!(benches); diff --git a/benches/year_2017_day_13.rs b/benches/year_2017_day_13.rs new file mode 100644 index 0000000..37856ab --- /dev/null +++ b/benches/year_2017_day_13.rs @@ -0,0 +1,60 @@ +use advent_rs::year_2017::day_13; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use itertools::Itertools; +use std::time::Duration; + +// Found here: https://colab.research.google.com/github/hhoppe/advent_of_code/blob/main/2017/advent_of_code_2017.ipynb#scrollTo=84e08a8d-ca2e-4bc7-99ca-8a29a98d0bf9&line=15&uniqifier=1 +pub fn day_13_v2_sieve(input: impl Into) -> u32 { + let firewalls: Vec<_> = input + .into() + .lines() + .map(|line| { + let mut tuple: (i32, i32) = line + .split(": ") + .map(|num| num.parse::().unwrap()) + .collect_tuple() + .unwrap(); + tuple.1 = (tuple.1 - 1) * 2; + tuple + }) + .collect(); + + let mut delay = 0; + let chunk: i32 = 200_000; + loop { + let mut ok = vec![true; chunk as usize]; + for (depth, range) in &firewalls { + let first = (-(delay as i32 + *depth as i32)) + .rem_euclid(*range as i32) + .abs(); + for idx in (first..chunk).step_by(*range as usize) { + ok[idx as usize] = false + } + } + if let Some(index) = ok.iter().position(|v| *v == true) { + return delay as u32 + index as u32; + } + delay += chunk as i32; + } +} + +fn bench_year_2017_day_13_v2(c: &mut Criterion) { + let mut group = c.benchmark_group("year_2017::day_13_v2"); + group.warm_up_time(Duration::from_millis(100)); + let input = include_str!("../inputs/year_2017/day_13_input"); + assert_eq!(day_13_v2_sieve(input), 3_875_838); + assert_eq!(day_13::day_13_v2(input), 3_875_838); + group.bench_with_input(BenchmarkId::new("Sieve", input.len()), input, |b, input| { + b.iter(|| day_13_v2_sieve(input)) + }); + group.bench_with_input( + BenchmarkId::new("Normal", input.len()), + input, + |b, input| b.iter(|| day_13::day_13_v2(input)), + ); + + group.finish(); +} + +criterion_group!(bench_year_2017_day_13, bench_year_2017_day_13_v2); +criterion_main!(bench_year_2017_day_13); diff --git a/inputs/year_2017/day_13_input b/inputs/year_2017/day_13_input new file mode 100644 index 0000000..93957aa --- /dev/null +++ b/inputs/year_2017/day_13_input @@ -0,0 +1,43 @@ +0: 3 +1: 2 +2: 4 +4: 4 +6: 5 +8: 6 +10: 6 +12: 8 +14: 6 +16: 6 +18: 8 +20: 12 +22: 8 +24: 8 +26: 9 +28: 8 +30: 8 +32: 12 +34: 20 +36: 10 +38: 12 +40: 12 +42: 10 +44: 12 +46: 12 +48: 12 +50: 12 +52: 12 +54: 14 +56: 14 +58: 12 +62: 14 +64: 14 +66: 14 +68: 14 +70: 14 +72: 14 +74: 14 +76: 14 +78: 14 +80: 18 +82: 17 +84: 14 diff --git a/src/year_2017.rs b/src/year_2017.rs index 42845eb..8b64e78 100644 --- a/src/year_2017.rs +++ b/src/year_2017.rs @@ -14,6 +14,7 @@ pub mod day_09; pub mod day_10; pub mod day_11; pub mod day_12; +pub mod day_13; pub fn solve(day: u8, part: u8, input: impl Into) -> Option { if part > 2 { @@ -33,6 +34,7 @@ pub fn solve(day: u8, part: u8, input: impl Into) -> Option { 10 => Some(day_10::day_10(part, input).to_string()), 11 => Some(day_11::day_11(part, input).to_string()), 12 => Some(day_12::day_12(part, input).to_string()), + 13 => Some(day_13::day_13(part, input).to_string()), _ => None, } } @@ -124,4 +126,11 @@ mod tests { assert_eq!(day_12::day_12_v1(input), 130); assert_eq!(day_12::day_12_v2(input), 189); } + + #[test] + fn day_13() { + let input = include_str!("../inputs/year_2017/day_13_input"); + assert_eq!(day_13::day_13_v1(input), 2_264); + assert_eq!(day_13::day_13_v2(input), 3_875_838); + } } diff --git a/src/year_2017/day_13.rs b/src/year_2017/day_13.rs new file mode 100644 index 0000000..f51a216 --- /dev/null +++ b/src/year_2017/day_13.rs @@ -0,0 +1,80 @@ +use itertools::Itertools; + +fn parse_input(input: &str) -> Vec<(i32, i32, i32)> { + input + .lines() + .map(|line| { + let tuple: (i32, i32) = line + .split(": ") + .map(|num| num.parse::().unwrap()) + .collect_tuple() + .unwrap(); + (tuple.0, tuple.1, (tuple.1 - 1) * 2) + }) + .collect() +} + +pub fn day_13_v1(input: impl Into) -> u32 { + let firewalls = parse_input(&input.into()); + let mut severity: u32 = 0; + for (depth, range, position) in firewalls.iter() { + if *depth == 0 || (depth % position) == 0 { + severity += *range as u32 * *depth as u32; + } + } + + severity +} + +#[inline] +fn traverse_firewall(firewalls: &[(i32, i32)], delay: u32) -> bool { + for (depth, range) in firewalls.iter() { + if ((*depth as u32 + delay) % *range as u32) == 0 { + return false; + } + } + true +} + +pub fn day_13_v2(input: impl Into) -> u32 { + let firewalls: Vec<_> = input + .into() + .lines() + .map(|line| { + let mut tuple: (i32, i32) = line + .split(": ") + .map(|num| num.parse::().unwrap()) + .collect_tuple() + .unwrap(); + tuple.1 = (tuple.1 - 1) * 2; + tuple + }) + .collect(); + let mut delay = 0; + while !traverse_firewall(&firewalls, delay) { + delay += 1; + } + delay +} + +solvable!(day_13, day_13_v1, day_13_v2, u32); + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = "0: 3\n\ + 1: 2\n\ + 4: 4\n\ + 6: 4"; + + #[test] + fn works_with_samples_v1() { + assert_eq!(day_13_v1(SAMPLE), 24); + } + + #[test] + fn works_with_samples_v2() { + assert_eq!(day_13_v2(SAMPLE), 10); + } +}