Grant Calculations in Two Formats

502 words
3 min.
Post preview image
Advent of Code 2025: Day 6 – applying column operators to number grids, with a cephalopod twist

Day 6 is a grid maths puzzle – but with a twist in Part 2 that caught me off guard!

The input is a grid of numbers with an operator row at the bottom:

 3  7  2  5
 4  1  8  3
 6  9  4  2
 *  +  *  +

Part 1

Each column has an operator (+ or *). Apply the operator to all numbers in that column, sum up all the column results:

function calcGrantTotal(numberRows: number[][], ops: string[]): number {
    let total: number = 0;
    const cols = numberRows[0].length;
    for (let i = 0; i < cols; i++) {
        const op = ops[i];
        const column: number[] = numberRows.map(row => row[i]);
        const result = column.reduce(
            (acc, cur) => op === '+' ? acc + cur : acc * cur,
            op === '+' ? 0 : 1
        );
        total += result;
    }
    return total;
}

In the example: column 0 gives 3*4*6 = 72, column 1 gives 7+1+9 = 17, column 2 gives 2*8*4 = 64, column 3 gives 5+3+2 = 10. Total: 163.


Part 2

Part 2 introduces the cephalopod format – a completely different layout. Multiple independent problems are placed side by side, separated by blank columns. Within each problem, columns are read right-to-left, and the operator appears after the numbers (to the right side).

function calcGrantTotalCephalopod(lines: string[]): number {
    const maxLen = Math.max(...lines.map(l => l.length));
    const grid = lines.map(line => line.padEnd(maxLen, ' '));
    const numRows = grid.length - 1;
    const opRow = grid[grid.length - 1];

    // Find separator columns (all-blank across every row)
    const separatorCols: boolean[] = Array.from({ length: maxLen }, (_, c) =>
        grid.every(row => row[c] === ' ')
    );

    // Extract spans between separators
    const spans: Array<[number, number]> = [];
    let start = 0;
    for (let c = 0; c <= maxLen; c++) {
        if (c === maxLen || separatorCols[c]) {
            if (start <= c - 1) spans.push([start, c - 1]);
            start = c + 1;
        }
    }

    let total = 0;
    for (let p = spans.length - 1; p >= 0; p--) {
        const [startCol, endCol] = spans[p];
        const op = opRow.slice(startCol, endCol + 1).split('').find(ch => ch !== ' ');
        if (!op) continue;
        const numbers: number[] = [];

        // Read columns right-to-left
        for (let c = endCol; c >= startCol; c--) {
            const chars = Array.from({ length: numRows }, (_, r) => grid[r][c]);
            if (chars.every(ch => ch === ' ')) continue;
            const digits = chars.filter(ch => ch !== ' ').join('');
            numbers.push(Number(digits));
        }

        total += numbers.reduce(
            (acc, cur) => op === '+' ? acc + cur : acc * cur,
            op === '+' ? 0 : 1
        );
    }
    return total;
}

The blank-column detection separates independent sub-problems, and then we traverse each one right-to-left to assemble the numbers before applying the operator. Definitely the trickiest parsing I’ve done so far this year!


Fresh Ingredients by the Range
Bean Plant Splits and Timelines