diff --git a/aoc2025/benches/aoc2025_bench.rs b/aoc2025/benches/aoc2025_bench.rs index 73c5a0c..6f8d4de 100644 --- a/aoc2025/benches/aoc2025_bench.rs +++ b/aoc2025/benches/aoc2025_bench.rs @@ -1,9 +1,11 @@ use criterion::{criterion_group, criterion_main, Criterion}; use aoc2025::day01; +use aoc2025::day02; fn aoc2025_all(c: &mut Criterion) { c.bench_function("day01", |b| b.iter(|| day01::run().unwrap())); + c.bench_function("day02", |b| b.iter(|| day02::run().unwrap())); } criterion_group! { diff --git a/aoc2025/input/day02.txt b/aoc2025/input/day02.txt new file mode 100644 index 0000000..2bc9ca3 --- /dev/null +++ b/aoc2025/input/day02.txt @@ -0,0 +1 @@ +52-75,71615244-71792700,89451761-89562523,594077-672686,31503-39016,733-976,1-20,400309-479672,458-635,836793365-836858811,3395595155-3395672258,290-391,5168-7482,4545413413-4545538932,65590172-65702074,25-42,221412-256187,873499-1078482,118-154,68597355-68768392,102907-146478,4251706-4487069,64895-87330,8664371543-8664413195,4091-5065,537300-565631,77-115,83892238-83982935,6631446-6694349,1112-1649,7725-9776,1453397-1493799,10240-12328,15873-20410,1925-2744,4362535948-4362554186,3078725-3256936,710512-853550,279817-346202,45515-60928,3240-3952 diff --git a/aoc2025/input/day02_provided.txt b/aoc2025/input/day02_provided.txt new file mode 100644 index 0000000..a3f22ef --- /dev/null +++ b/aoc2025/input/day02_provided.txt @@ -0,0 +1 @@ +11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124 diff --git a/aoc2025/src/day02.rs b/aoc2025/src/day02.rs new file mode 100644 index 0000000..3a119df --- /dev/null +++ b/aoc2025/src/day02.rs @@ -0,0 +1,186 @@ +use anyhow::{anyhow, Context, Result}; +use std::{fmt::Write, ops::RangeInclusive, str::FromStr}; + +const INPUT: &str = include_str!("../input/day02.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + writeln!(res, "part 1: {}", part1(INPUT)?)?; + writeln!(res, "part 2: {}", part2(INPUT)?)?; + Ok(res) +} + +struct IdRange(RangeInclusive); + +impl FromStr for IdRange { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let (left, right) = s + .split_once('-') + .ok_or_else(|| anyhow!("couldn't find dash in range: `{}'", s))?; + let (left, right) = ( + left.parse() + .with_context(|| format!("couldn't parse left member of range: `{}'", left))?, + right + .trim() + .parse() + .with_context(|| format!("couldn't parse right member of range: `{}'", right))?, + ); + + Ok(IdRange(left..=right)) + } +} + +impl Iterator for IdRange { + type Item = as Iterator>::Item; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +fn get_num_digits(num: &u64) -> usize { + let mut digits = 0; + let mut num = *num; + while num != 0 { + num /= 10; + digits += 1; + } + digits +} + +fn is_repeated_twice(num: &u64) -> bool { + let num_digits = get_num_digits(num); + if !num_digits.is_multiple_of(2) { + return false; + } + let half = num_digits / 2; + let mask = 10_u64.pow(half as u32); + + *num / mask == *num % mask +} + +fn part1(input: &str) -> Result { + let ranges = input + .split(',') + .map(IdRange::from_str) + .collect::>>()?; + + Ok(ranges + .into_iter() + .map(|range| range.filter(is_repeated_twice).sum::()) + .sum()) +} + +fn has_identical_chunks(num: &u64, chunk_size: usize) -> bool { + // build a "mask" for our chunk size + let mask = 10_u64.pow(chunk_size as u32); + + // this is what all chunks should look like to satisfy our condition + let target = *num % mask; + + let mut num = *num; + while num > target { + // shift num right by 10^(sub_digit_num) + num /= mask; + if num % mask != target { + return false; + } + } + + true +} + +fn is_repeated_at_least_twice(num: &u64) -> bool { + let num_digits = get_num_digits(num); + if num_digits < 2 { + return false; + } + + has_identical_chunks(num, 1) + || (2..=(num_digits / 2)).any(|n| { + // walk all possible divisors of our number of digits + if !num_digits.is_multiple_of(n) { + return false; + } + let chunk_size = num_digits / n; + has_identical_chunks(num, chunk_size) + }) +} + +fn part2(input: &str) -> Result { + let ranges = input + .split(',') + .map(IdRange::from_str) + .collect::>>()?; + + Ok(ranges + .into_iter() + .map(|range| range.filter(is_repeated_at_least_twice).sum::()) + .sum()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED: &str = include_str!("../input/day02_provided.txt"); + + #[test] + fn repeated_twice() { + assert!(is_repeated_twice(&11)); + assert!(is_repeated_twice(&22)); + assert!(is_repeated_twice(&99)); + assert!(is_repeated_twice(&1010)); + assert!(is_repeated_twice(&1188511885)); + assert!(is_repeated_twice(&222222)); + assert!(is_repeated_twice(&446446)); + assert!(is_repeated_twice(&38593859)); + + assert!(!is_repeated_twice(&111)); + assert!(!is_repeated_twice(&121)); + assert!(!is_repeated_twice(&1)); + } + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED).unwrap(), 1227775554); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 26255179562); + } + + #[test] + fn repeated_at_least_twice() { + assert!(is_repeated_at_least_twice(&11)); + assert!(is_repeated_at_least_twice(&22)); + assert!(is_repeated_at_least_twice(&99)); + assert!(is_repeated_at_least_twice(&111)); + assert!(is_repeated_at_least_twice(&999)); + assert!(is_repeated_at_least_twice(&1010)); + assert!(is_repeated_at_least_twice(&1188511885)); + assert!(is_repeated_at_least_twice(&222222)); + assert!(is_repeated_at_least_twice(&446446)); + assert!(is_repeated_at_least_twice(&38593859)); + assert!(is_repeated_at_least_twice(&565656)); + assert!(is_repeated_at_least_twice(&824824824)); + assert!(is_repeated_at_least_twice(&2121212121)); + + assert!(!is_repeated_at_least_twice(&1121)); + assert!(!is_repeated_at_least_twice(&121)); + assert!(!is_repeated_at_least_twice(&1)); + } + + #[test] + fn part2_provided() { + assert_eq!(part2(PROVIDED).unwrap(), 4174379265); + } + + #[test] + fn part2_real() { + assert_eq!(part2(INPUT).unwrap(), 31680313976); + } +} diff --git a/aoc2025/src/lib.rs b/aoc2025/src/lib.rs index 12b8f18..28326d5 100644 --- a/aoc2025/src/lib.rs +++ b/aoc2025/src/lib.rs @@ -1 +1,2 @@ pub mod day01; +pub mod day02; diff --git a/aoc2025/src/main.rs b/aoc2025/src/main.rs index 87fb660..1cb26fa 100644 --- a/aoc2025/src/main.rs +++ b/aoc2025/src/main.rs @@ -3,9 +3,10 @@ use anyhow::Result; use aoc::DayFunc; use aoc2025::day01; +use aoc2025::day02; fn main() -> Result<()> { - let days: &[DayFunc] = &[day01::run]; + let days: &[DayFunc] = &[day01::run, day02::run]; aoc::run(days) }