From cac276c7dc5032ea504c07f040ef3d4048d2d70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 10 Dec 2025 11:14:15 +0000 Subject: [PATCH] feat: 2025 day 03 --- .github/badges/completion2025.json | 2 +- 2025/day03/Cargo.toml | 31 +++++++ 2025/day03/benches/benchmarks.rs | 18 ++++ 2025/day03/src/common.rs | 127 +++++++++++++++++++++++++++++ 2025/day03/src/lib.rs | 68 +++++++++++++++ 2025/day03/src/main.rs | 22 +++++ Cargo.toml | 3 +- common/src/benchmark.rs | 6 +- common/src/constants.rs | 24 ++++++ solution-runner/Cargo.toml | 1 + solution-runner/src/main.rs | 1 + 11 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 2025/day03/Cargo.toml create mode 100644 2025/day03/benches/benchmarks.rs create mode 100644 2025/day03/src/common.rs create mode 100644 2025/day03/src/lib.rs create mode 100644 2025/day03/src/main.rs diff --git a/.github/badges/completion2025.json b/.github/badges/completion2025.json index 2a97368..19bd03f 100644 --- a/.github/badges/completion2025.json +++ b/.github/badges/completion2025.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "label": "2025", - "message": "04/50", + "message": "06/50", "color": "red", "style": "for-the-badge" } diff --git a/2025/day03/Cargo.toml b/2025/day03/Cargo.toml new file mode 100644 index 0000000..567e81c --- /dev/null +++ b/2025/day03/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "day03_2025" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "day03_2025" +path = "src/lib.rs" + +[dependencies] +aoc-solution = { path = "../../aoc-solution" } +aoc-common = { path = "../../common" } +anyhow = { workspace = true } +winnow = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "benchmarks" +harness = false + +[lints] +workspace = true \ No newline at end of file diff --git a/2025/day03/benches/benchmarks.rs b/2025/day03/benches/benchmarks.rs new file mode 100644 index 0000000..67b9cbd --- /dev/null +++ b/2025/day03/benches/benchmarks.rs @@ -0,0 +1,18 @@ +// Copyright 2023 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use aoc_common::define_aoc_benchmark; +use day03_2025::Day03; + +define_aoc_benchmark!("inputs/2025/day03", Day03); diff --git a/2025/day03/src/common.rs b/2025/day03/src/common.rs new file mode 100644 index 0000000..e330e6b --- /dev/null +++ b/2025/day03/src/common.rs @@ -0,0 +1,127 @@ +// Copyright 2024 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Context; +use aoc_common::helpers::digits_to_number; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub struct BatteryBank { + pub batteries: Vec, +} + +impl BatteryBank { + fn maximum_joltage(&self, num_digits: usize) -> usize { + let mut digits = Vec::new(); + let mut remaining_to_choose = num_digits; + + let len = self.batteries.len(); + if len < num_digits { + return 0; + } + if len == num_digits { + return digits_to_number(&self.batteries); + } + + let mut window_start = 0; + for _ in 0..num_digits { + let mut chosen = 0; + + // our unused window must always be sufficiently big for the remaining digits + let selection_window = &self.batteries[window_start..len - remaining_to_choose + 1]; + + // check if we simply run out of choices + if self.batteries[window_start..].len() == remaining_to_choose { + digits.extend_from_slice(&self.batteries[window_start..]); + break; + } + + let mut found_at_index = 0; + for (i, value) in selection_window.iter().enumerate() { + if *value > chosen { + chosen = *value; + found_at_index = i; + } + if *value == 9 { + break; + } + } + window_start += found_at_index + 1; + remaining_to_choose -= 1; + + digits.push(chosen); + } + + digits_to_number(&digits) + } + + pub fn maximum_joltage_with_two(&self) -> usize { + self.maximum_joltage(2) + } + + pub fn maximum_joltage_with_twelve(&self) -> usize { + self.maximum_joltage(12) + } +} + +impl FromStr for BatteryBank { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let batteries = s + .chars() + .map(|c| { + c.to_digit(10) + .map(|d| d as usize) + .context("invalid battery value") + }) + .collect::, _>>()?; + Ok(BatteryBank { batteries }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn maximum_joltage_with_two() { + let bank = BatteryBank::from_str("987654321111111").unwrap(); + assert_eq!(bank.maximum_joltage_with_two(), 98); + + let bank = BatteryBank::from_str("811111111111119").unwrap(); + assert_eq!(bank.maximum_joltage_with_two(), 89); + + let bank = BatteryBank::from_str("234234234234278").unwrap(); + assert_eq!(bank.maximum_joltage_with_two(), 78); + + let bank = BatteryBank::from_str("818181911112111").unwrap(); + assert_eq!(bank.maximum_joltage_with_two(), 92); + } + + #[test] + fn maximum_joltage_with_twelve() { + let bank = BatteryBank::from_str("987654321111111").unwrap(); + assert_eq!(bank.maximum_joltage_with_twelve(), 987654321111); + + let bank = BatteryBank::from_str("811111111111119").unwrap(); + assert_eq!(bank.maximum_joltage_with_twelve(), 811111111119); + + let bank = BatteryBank::from_str("234234234234278").unwrap(); + assert_eq!(bank.maximum_joltage_with_twelve(), 434234234278); + + let bank = BatteryBank::from_str("818181911112111").unwrap(); + assert_eq!(bank.maximum_joltage_with_twelve(), 888911112111); + } +} diff --git a/2025/day03/src/lib.rs b/2025/day03/src/lib.rs new file mode 100644 index 0000000..efc9ae5 --- /dev/null +++ b/2025/day03/src/lib.rs @@ -0,0 +1,68 @@ +// Copyright 2024 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::common::BatteryBank; +use aoc_common::parsing::LineParser; +use aoc_solution::Aoc; + +mod common; + +#[derive(Aoc)] +#[aoc(input = Vec)] +#[aoc(parser = LineParser)] +#[aoc(part1(output = usize, runner = part1))] +#[aoc(part2(output = usize, runner = part2))] +pub struct Day03; + +pub fn part1(input: Vec) -> usize { + input + .into_iter() + .map(|b| b.maximum_joltage_with_two()) + .sum() +} + +pub fn part2(input: Vec) -> usize { + input + .into_iter() + .map(|b| b.maximum_joltage_with_twelve()) + .sum() +} + +#[cfg(test)] +mod tests { + use super::*; + use aoc_solution::parser::AocInputParser; + + fn sample_input() -> Vec { + LineParser::parse_input( + r#"987654321111111 +811111111111119 +234234234234278 +818181911112111"#, + ) + .unwrap() + } + + #[test] + fn part1_sample_input() { + let expected = 357; + assert_eq!(expected, part1(sample_input())) + } + + #[test] + fn part2_sample_input() { + let expected = 3121910778619; + assert_eq!(expected, part2(sample_input())) + } +} diff --git a/2025/day03/src/main.rs b/2025/day03/src/main.rs new file mode 100644 index 0000000..66bc5fd --- /dev/null +++ b/2025/day03/src/main.rs @@ -0,0 +1,22 @@ +// Copyright 2024 Jedrzej Stuczynski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use aoc_common::helpers::root_path; +use aoc_solution::AocSolutionSolver; +use day03_2025::Day03; + +#[cfg(not(tarpaulin_include))] +fn main() { + Day03::try_solve_from_file(root_path("inputs/2025/day03")) +} diff --git a/Cargo.toml b/Cargo.toml index 4797962..bc92dc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,8 @@ members = [ "2024/day10", "2024/day11", "2025/day01", - "2025/day02" + "2025/day02", + "2025/day03" ] [workspace.package] diff --git a/common/src/benchmark.rs b/common/src/benchmark.rs index f6c23fa..a6153c5 100644 --- a/common/src/benchmark.rs +++ b/common/src/benchmark.rs @@ -19,7 +19,7 @@ macro_rules! define_aoc_benchmark { use aoc_common::helpers::root_path; use aoc_common::input_read::read_input; - use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; + use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use std::fs; fn get_input() -> <$typ as AocSolution>::Input { @@ -42,7 +42,7 @@ macro_rules! define_aoc_benchmark { c.bench_function(&bench_name, move |b| { b.iter_batched( || input.clone(), - |input| <$typ as AocSolution>::part1((black_box(input))), + |input| <$typ as AocSolution>::part1((std::hint::black_box(input))), BatchSize::SmallInput, ) }); @@ -54,7 +54,7 @@ macro_rules! define_aoc_benchmark { c.bench_function(&bench_name, move |b| { b.iter_batched( || input.clone(), - |input| <$typ as AocSolution>::part2((black_box(input))), + |input| <$typ as AocSolution>::part2((std::hint::black_box(input))), BatchSize::SmallInput, ) }); diff --git a/common/src/constants.rs b/common/src/constants.rs index 50cc2c7..9b90671 100644 --- a/common/src/constants.rs +++ b/common/src/constants.rs @@ -14,3 +14,27 @@ pub const FILLED_PIXEL: char = '█'; pub const EMPTY_PIXEL: char = '⠀'; + +pub const POWERS_OF_TEN: [usize; 21] = [ + 0, + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000, +]; diff --git a/solution-runner/Cargo.toml b/solution-runner/Cargo.toml index 175ff3b..6a95b2a 100644 --- a/solution-runner/Cargo.toml +++ b/solution-runner/Cargo.toml @@ -111,3 +111,4 @@ day10_2024 = { path = "../2024/day10" } day11_2024 = { path = "../2024/day11" } day01_2025 = { path = "../2025/day01" } day02_2025 = { path = "../2025/day02" } +day03_2025 = { path = "../2025/day03" } diff --git a/solution-runner/src/main.rs b/solution-runner/src/main.rs index a623533..8f3f897 100644 --- a/solution-runner/src/main.rs +++ b/solution-runner/src/main.rs @@ -133,6 +133,7 @@ fn main() { define_solution!(args, 2024, 11, "inputs/2024/day11", day11_2024::Day11); define_solution!(args, 2025, 1, "inputs/2025/day01", day01_2025::Day01); define_solution!(args, 2025, 2, "inputs/2025/day02", day02_2025::Day02); + define_solution!(args, 2025, 3, "inputs/2025/day03", day03_2025::Day03); // AUTOGENERATED SOLUTIONS END println!("no solution found for year {}, day {}", args.year, args.day);