aoc/year2025/
day02.rs

1//! # Gift Shop
2//!
3//! We solve efficiently by inverting the problem and avoiding slow string manipulation.
4//! Instead of checking every possible id within each range for invalid patterns, we generate a list
5//! of invalid patterns numerically then check how many overlap the range given by each pair of ids.
6//!
7//! Let `(n,k)` denote the set of patterns `k` wide in a number `n` digits long, where `k` must
8//! divide `n` evenly. For example some `(6,3)` patterns are 123123, 456456 and 789789.
9//!
10//! ## Part One
11//!
12//! The input contains ids ranging from 2 to 10 digits, so we check for patterns where `k`
13//! is half of `n`, that are `(2, 1)`, `(4, 2)`, `(6, 3)`, `(8, 4)`, and `(10, 5)`.
14//!
15//! ## Part Two
16//!
17//! We also consider the remaining patterns where `n` is evenly divisible by `k`. However we need
18//! to be careful to avoid double counting. We notice that pattern sets with the same `n` contain
19//! other sets when the second `k` is a factor of the first. For example `(8,4)` contains `(8,2)`,
20//! as a number such as 23232323 can be split into either two fours or four twos.
21//! All sets `(n, k)` contain `(n, 1)`, for example 22222222 could be split into four, two or one.
22//!
23//! Our high level approach is then:
24//! * Count ids in part one
25//! * Count the extra ids in part two, subtracting those that overlap.
26//!
27//! ## Generating ranges
28//!
29//! Let's use a concrete example of `(9,3)`. This range starts at 100100100 and we can generate
30//! the next number in the set by adding 001001001. The step value is given mathematically as
31//! `(10ⁿ - 1) / (10ᵏ - 1)`, that is 999999999 / 999 = 1001001. The start value is then `10ᵏ⁻¹`
32//! times the step (100 * 1001001 = 100100100) and the end value is `10ᵏ - 1` times the step
33//! (999 * 1001001 = 999999999).
34//!
35//! We then check each range of ids for the minimum and maximum multiple of the step value that it
36//! contains, clamping at the start and end values. For example, the id range 50-70 contains
37//! 55 and 66 from the set `(2,1)` with a step of 11. A second id range of 100-130 also
38//! contains multiples of 11 but these are ignored as they are outside the start and end values.
39//!
40//! To sum the set values within each range we use the
41//! [triangular number](https://en.wikipedia.org/wiki/Triangular_number) formula
42//! `(n * (n + 1)) / 2` that sums the numbers from 1 to `n`. For example:
43//!
44//! * `44 + 55 + 66 + 77`
45//! * `(4 * 44) + 11 + 22 + 33`
46//! * `(4 * 44) + 11 * (1 + 2 + 3)`,
47//! * Replace `1 + 2 + 3` with the formula.
48use crate::util::iter::*;
49use crate::util::parse::*;
50
51type Range = [u32; 2];
52type Pair = [u64; 2];
53
54/// Sets in part one.
55const FIRST: [Range; 5] = [[2, 1], [4, 2], [6, 3], [8, 4], [10, 5]];
56/// Sets in part two.
57const SECOND: [Range; 6] = [[3, 1], [5, 1], [6, 2], [7, 1], [9, 3], [10, 2]];
58/// Overlap between sets in part one and part two.
59const THIRD: [Range; 2] = [[6, 1], [10, 1]];
60
61pub fn parse(input: &str) -> Vec<Pair> {
62    input.iter_unsigned::<u64>().chunk::<2>().collect()
63}
64
65pub fn part1(input: &[Pair]) -> u64 {
66    sum(&FIRST, input)
67}
68
69pub fn part2(input: &[Pair]) -> u64 {
70    sum(&FIRST, input) + sum(&SECOND, input) - sum(&THIRD, input)
71}
72
73/// Generate the start and end values for a set,
74/// then sum the number of values contained in the given id range.
75fn sum(ranges: &[Range], input: &[Pair]) -> u64 {
76    let mut result = 0;
77
78    for &[digits, size] in ranges {
79        // Generate the sequence of invalid digit ids numerically.
80        let digits_power = 10_u64.pow(digits);
81        let size_power = 10_u64.pow(size);
82
83        let step = (digits_power - 1) / (size_power - 1);
84        let start = step * (size_power / 10);
85        let end = step * (size_power - 1);
86
87        for &[from, to] in input {
88            // Find the first and last multiple of the step size,
89            // clamping to the start and end of the set.
90            let lower = from.next_multiple_of(step).max(start);
91            let upper = to.min(end);
92
93            // Sum invalid ids using triangular number formula.
94            if lower <= upper {
95                let n = (upper - lower) / step;
96                let triangular = n * (n + 1) / 2;
97                result += lower * (n + 1) + step * triangular;
98            }
99        }
100    }
101
102    result
103}