Compare commits

...

2 commits

Author SHA1 Message Date
ef970ed47a 2025: day02: part 2 2025-12-03 18:13:18 +01:00
f0dc93b0bb 2025: day02: part 1 2025-12-03 18:13:18 +01:00
6 changed files with 193 additions and 1 deletions

View file

@ -1,9 +1,11 @@
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use aoc2025::day01; use aoc2025::day01;
use aoc2025::day02;
fn aoc2025_all(c: &mut Criterion) { fn aoc2025_all(c: &mut Criterion) {
c.bench_function("day01", |b| b.iter(|| day01::run().unwrap())); c.bench_function("day01", |b| b.iter(|| day01::run().unwrap()));
c.bench_function("day02", |b| b.iter(|| day02::run().unwrap()));
} }
criterion_group! { criterion_group! {

1
aoc2025/input/day02.txt Normal file
View file

@ -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

View file

@ -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

186
aoc2025/src/day02.rs Normal file
View file

@ -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<String> {
let mut res = String::with_capacity(128);
writeln!(res, "part 1: {}", part1(INPUT)?)?;
writeln!(res, "part 2: {}", part2(INPUT)?)?;
Ok(res)
}
struct IdRange(RangeInclusive<u64>);
impl FromStr for IdRange {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
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 = <RangeInclusive<u64> as Iterator>::Item;
fn next(&mut self) -> Option<Self::Item> {
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<u64> {
let ranges = input
.split(',')
.map(IdRange::from_str)
.collect::<Result<Vec<_>>>()?;
Ok(ranges
.into_iter()
.map(|range| range.filter(is_repeated_twice).sum::<u64>())
.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<u64> {
let ranges = input
.split(',')
.map(IdRange::from_str)
.collect::<Result<Vec<_>>>()?;
Ok(ranges
.into_iter()
.map(|range| range.filter(is_repeated_at_least_twice).sum::<u64>())
.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);
}
}

View file

@ -1 +1,2 @@
pub mod day01; pub mod day01;
pub mod day02;

View file

@ -3,9 +3,10 @@ use anyhow::Result;
use aoc::DayFunc; use aoc::DayFunc;
use aoc2025::day01; use aoc2025::day01;
use aoc2025::day02;
fn main() -> Result<()> { fn main() -> Result<()> {
let days: &[DayFunc] = &[day01::run]; let days: &[DayFunc] = &[day01::run, day02::run];
aoc::run(days) aoc::run(days)
} }