Day 2/2025 - Repeated Sequences

The second puzzle of 2025's AoC (available at https://adventofcode.com/2025/day/2) has us searching through ranges of numbers for those with repeated digit patterns.

The input is a single line containing comma-separated ranges like 11-99,100-200. For each range, we need to find numbers with specific repetition properties.

Parsing the Input

The input format requires a bit of nested splitting:

type Range = [number, number];

const processInput = (day: number): Range[] => {
    const line = readInput(day);
    return line.split(",").map(part => 
        part.split("-").map(s => Number(s)) as Range
    );
};

We split on commas to get individual ranges, then split each range on the dash to get start and end values. The as Range assertion tells TypeScript this is a tuple of exactly two numbers. In production code I'd add validation, but for AoC we trust the input.

Part One: Finding Doubles

For part one, we need to find numbers where the first half of digits equals the second half, then sum them. Numbers like 1212, 5555, or 123123 qualify.

The approach is straightforward:

const checkDouble = (value: string): boolean => {
    if (value.length % 2 !== 0) {
        return false; // Odd length can never be a "double"
    }
    const half = value.length / 2;
    const firstHalf = value.substring(0, half);
    const secondHalf = value.substring(half);
    return firstHalf === secondHalf;
};

const partOne = (input: Range[], debug: boolean) => {
    let total = 0;
    for (const [start, end] of input) {
        for (let nValue = start; nValue <= end; nValue++) {
            const value = nValue.toString();
            if (checkDouble(value)) {
                total += nValue;
            }
        }
    }
    return total;
};

A few notes:

  • Odd-length numbers are immediately skipped — you can't split 12345 into two equal halves
  • We work with strings for the comparison since we care about digit patterns, not numeric properties
  • We sum the actual numbers, not count them

Part Two: Finding Repeated Sequences

Part two ups the ante. Now we need to find numbers composed of any sequence repeated at least twice. So 123123 (period 3, repeated twice), 111111 (period 1, repeated six times), and 21212121 (period 2, repeated four times) all qualify.

The key insight is that for a number with n digits, we only need to check periods that evenly divide n. A 12-digit number could have periods of 1, 2, 3, 4, or 6.

const checkMultiple = (value: string): boolean => {
    const len = value.length;
    
    for (let period = 1; period <= len / 2; period++) {
        if (len % period !== 0) {
            continue; // Period must evenly divide the length
        }
        
        const segment = value.substring(0, period);
        const repetitions = len / period;
        const target = segment.repeat(repetitions);
        
        if (target === value) {
            return true;
        }
    }
    
    return false;
};

JavaScript's String.prototype.repeat() does the heavy lifting here. We extract the first period characters, repeat them enough times to match the original length, and compare.

Note that we don't need to worry about double-counting. If 111111 matches for period 1, we return immediately — we don't care that it would also match for period 2 or 3.

Performance

The brute force approach iterates through every number in every range. With ranges potentially spanning millions of numbers, this could be slow.

But it isn't. The full input runs in about 228 milliseconds.

Could we optimize? Absolutely. For part one, given a range like 1000000-9999999, we could mathematically calculate how many doubles exist without checking each number. But why bother? The code is clear, correct, and fast enough.

Takeaways

  1. String operations for digit manipulation: When you care about the structure of digits rather than their numeric value, convert to string early. The substring and repeat operations are cleaner than modulo arithmetic.

  2. Brute force has its place: Both parts iterate through potentially millions of numbers. It runs in under 250ms. Sometimes the "naive" solution is the right solution.

  3. Separation of concerns: Extracting checkDouble and checkMultiple into separate functions made refactoring from part one to part two trivial. The main loop stayed identical.

  4. Readability over cleverness: This code doesn't use any tricks. Anyone reading it can understand what it does. That's often more valuable than shaving off a few milliseconds.

The full solution is available in the repository.