Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/badges/completion2025.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "2025",
"message": "02/50",
"message": "04/50",
"color": "red",
"style": "for-the-badge"
}
31 changes: 31 additions & 0 deletions 2025/day02/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "day02_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 = "day02_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
18 changes: 18 additions & 0 deletions 2025/day02/benches/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -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 day02_2025::Day02;

define_aoc_benchmark!("inputs/2025/day02", Day02);
164 changes: 164 additions & 0 deletions 2025/day02/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// 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::{Digits, digits_to_number};
use aoc_common::parsing::combinators::parse_range_inclusive;
use std::ops::{Deref, RangeInclusive};
use std::str::FromStr;
use winnow::Parser;

#[derive(Clone, Debug)]
pub struct IdRange(RangeInclusive<usize>);

impl IdRange {
// no point in checking each value individually,
// we just iterate through all valid prefixes
pub fn invalid_ids_p1(&self) -> Vec<usize> {
if self.is_empty() {
return Vec::new();
}

let mut invalid_ids = Vec::new();

// if our prefix digit count is odd, we start from the smallest next number with even count of digits
let mut start_digits = self.0.start().to_digits();
if !start_digits.len().is_multiple_of(2) {
let start = next_smallest_number_with_more_digits(&start_digits);
start_digits = start.to_digits();
}

let mid_point = start_digits.len() / 2;

let mut prefix_digits = start_digits[..mid_point].to_vec();
let mut prefix = digits_to_number(&prefix_digits);
loop {
let candidate_id = prefix_digits_to_id(&prefix_digits);
if self.0.contains(&candidate_id) {
invalid_ids.push(candidate_id);
} else if candidate_id > *self.0.start() {
break;
}

prefix += 1;
prefix_digits = prefix.to_digits();
}

invalid_ids
}

pub fn invalid_ids_p2(&self) -> Vec<usize> {
// don't try to be too fancy here,
// just check every sequence
// (because I gave up trying to be fancy)
let mut invalid_ids = Vec::new();

for candidate in *self.0.start()..=*self.0.end() {
let digits = candidate.to_digits();
let digits_len = digits.len();
if invalid_ids.contains(&candidate) {
continue;
}

for seq_len in 1..=digits_len / 2 {
// can't possibly be a repeating sequence
if digits_len % seq_len != 0 {
continue;
}

let repeats = digits_len / seq_len;
// task requires at least 2 repeats
if repeats < 2 {
continue;
}

let pattern = &digits[..seq_len];
if pattern.repeat(repeats) == digits {
invalid_ids.push(candidate)
}
}
}

invalid_ids
}
}

// converts, e.g. [1,2,3] into 123123
fn prefix_digits_to_id(digits: &[usize]) -> usize {
usize::from_digits(&digits.repeat(2))
}

/// Returns the next smallest number with higher count of digits,
/// e.g. [9,9,9] returns 1000
/// [1,2,3,4] returns 10000
fn next_smallest_number_with_more_digits(digits: &[usize]) -> usize {
10usize.pow(digits.len() as u32 + 1)
}

impl Deref for IdRange {
type Target = RangeInclusive<usize>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl FromStr for IdRange {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(IdRange(
parse_range_inclusive
.parse(s)
.map_err(|err| anyhow::format_err!("{err}"))?,
))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn invalid_ids_p1() {
let range = IdRange::from_str("11-44").unwrap();
assert_eq!(range.invalid_ids_p1(), vec![11, 22, 33, 44]);

let range = IdRange::from_str("47-126").unwrap();
assert_eq!(range.invalid_ids_p1(), vec![55, 66, 77, 88, 99]);

let range = IdRange::from_str("99-1011").unwrap();
assert_eq!(range.invalid_ids_p1(), vec![99, 1010]);

let range = IdRange::from_str("999-1011").unwrap();
assert_eq!(range.invalid_ids_p1(), vec![1010])
}

#[test]
fn invalid_ids_p2() {
let range = IdRange::from_str("11-22").unwrap();
assert_eq!(range.invalid_ids_p2(), vec![11, 22]);

let range = IdRange::from_str("95-115").unwrap();
assert_eq!(range.invalid_ids_p2(), vec![99, 111]);

let range = IdRange::from_str("123123122-123123124").unwrap();
assert_eq!(range.invalid_ids_p2(), vec![123123123]);

let range = IdRange::from_str("998-1012").unwrap();
assert_eq!(range.invalid_ids_p2(), vec![999, 1010]);

let range = IdRange::from_str("824824821-824824827").unwrap();
assert_eq!(range.invalid_ids_p2(), vec![824824824])
}
}
64 changes: 64 additions & 0 deletions 2025/day02/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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::IdRange;
use aoc_common::parsing::CommaSeparatedParser;
use aoc_solution::Aoc;

mod common;

#[derive(Aoc)]
#[aoc(input = Vec<IdRange>)]
#[aoc(parser = CommaSeparatedParser)]
#[aoc(part1(output = usize, runner = part1))]
#[aoc(part2(output = usize, runner = part2))]
pub struct Day02;

pub fn part1(input: Vec<IdRange>) -> usize {
input
.into_iter()
.flat_map(|range| range.invalid_ids_p1().into_iter())
.sum()
}

pub fn part2(input: Vec<IdRange>) -> usize {
input
.into_iter()
.flat_map(|range| range.invalid_ids_p2().into_iter())
.sum()
}

#[cfg(test)]
mod tests {
use super::*;
use aoc_solution::parser::AocInputParser;

fn sample_input() -> Vec<IdRange> {
CommaSeparatedParser::parse_input(
r#"11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"#,
).unwrap()
}

#[test]
fn part1_sample_input() {
let expected = 1227775554;
assert_eq!(expected, part1(sample_input()))
}

#[test]
fn part2_sample_input() {
let expected = 4174379265;
assert_eq!(expected, part2(sample_input()))
}
}
22 changes: 22 additions & 0 deletions 2025/day02/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 day02_2025::Day02;

#[cfg(not(tarpaulin_include))]
fn main() {
Day02::try_solve_from_file(root_path("inputs/2025/day02"))
}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ members = [
"2024/day09",
"2024/day10",
"2024/day11",
"2025/day01"
"2025/day01",
"2025/day02"
]

[workspace.package]
Expand Down
22 changes: 22 additions & 0 deletions common/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ pub fn root_path<P: AsRef<Path>>(segment: P) -> PathBuf {
PathBuf::from(COMMON_ROOT).join("..").join(segment)
}

pub trait Digits {
fn to_digits(&self) -> Vec<usize>;

fn to_digits_reversed(&self) -> Vec<usize>;

fn from_digits(digits: &[usize]) -> Self;
}

impl Digits for usize {
fn to_digits(&self) -> Vec<usize> {
split_into_digits(*self)
}

fn to_digits_reversed(&self) -> Vec<usize> {
split_into_digits_reversed(*self)
}

fn from_digits(digits: &[usize]) -> Self {
digits_to_number(digits)
}
}

#[inline]
pub fn split_into_digits(number: usize) -> Vec<usize> {
let mut digits = Vec::new();
Expand Down
Loading
Loading