advent-of-code/aoc2025/src/day02.rs
Antoine Martin 7433687d69 2025: fix cargo fmt
Turns out rustfmt needs a --edition 2024 flag (which cargo fmt adds
automatically), otherwise some imports change order. Of course my editor
is configured to use rustfmt via apheleia, which does not support cargo
fmt. Urgh.
2025-12-08 16:39:16 +01:00

180 lines
4.8 KiB
Rust

use anyhow::{Context, Result, anyhow};
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 {
num.ilog10() as usize + 1
}
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);
}
}