day2: clever and fast solution

This commit is contained in:
2025-12-05 04:29:55 -08:00
parent 09cd17d3ff
commit f51dc9c145

View File

@@ -1,6 +1,7 @@
use aoc_runner_derive::{aoc, aoc_generator};
use itertools::Itertools;
use misc::POW10;
use std::cmp::{max, min};
use std::ops::RangeInclusive;
#[aoc_generator(day2)]
@@ -88,15 +89,15 @@ fn part2_arithmetic_brute(input: &[RangeInclusive<u64>]) -> u64 {
let n_digits = (product.ilog10() + 1) as usize;
for n in 1..=n_digits / 2 {
let repetitions = n_digits / n;
if !n_digits.is_multiple_of(n) {
continue;
}
let decade = POW10[n_digits - n];
let repetitions = n_digits / n;
let decade = POW10[n_digits - n]; // get the power of 10 to split the leftmost n digits
let lhs = product / decade;
let remainder = product % decade;
// for each repetition we multiply by 10^(rep * n)
// for each repetition we multiply by 10^(rep * n) to add the appropriate zeros
let expected_remainder = (0..repetitions - 1).map(|rep| lhs * POW10[rep * n]).sum();
if remainder == expected_remainder {
@@ -139,6 +140,48 @@ fn part1_clever(input: &[RangeInclusive<u64>]) -> u64 {
invalid_sum
}
const fn n_digits(n: &u64) -> usize {
(n.ilog10() + 1) as usize
}
fn generate_repeaters(r: &RangeInclusive<u64>, n: usize) -> Vec<u64> {
let mut invalids = Vec::new();
for r in split_by_decade(r) {
let n_digits = n_digits(r.start());
if !n_digits.is_multiple_of(n) || n_digits < 2 {
continue;
}
let repetitions = n_digits / n;
let decade = POW10[n_digits - n];
for lhs in (r.start() / decade)..=(r.end() / decade) {
let repeater = (0..repetitions)
.map(|rep| lhs * POW10[rep * n])
.sum::<u64>();
if r.contains(&repeater) {
invalids.push(repeater)
}
}
}
invalids
}
fn split_by_decade(r: &RangeInclusive<u64>) -> impl Iterator<Item = RangeInclusive<u64>> {
let (start, end) = (*r.start(), *r.end());
(n_digits(r.start())..n_digits(r.end()) + 1)
.map(move |nd| RangeInclusive::new(max(start, POW10[nd - 1]), min(end, POW10[nd] - 1)))
}
#[aoc(day2, part2, Clever)]
fn part2_clever(input: &[RangeInclusive<u64>]) -> u64 {
input
.iter()
.flat_map(|r| (1..=n_digits(r.end()) / 2).map(|nd| generate_repeaters(r, nd)))
.flatten()
.unique()
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -167,4 +210,8 @@ mod tests {
fn part1_clever_example() {
assert_eq!(part1_clever(&parse(EXAMPLE)), 1227775554);
}
#[test]
fn part2_clever_example() {
assert_eq!(part2_clever(&parse(EXAMPLE)), 4174379265);
}
}