Day six of AoC 2025 (available at https://adventofcode.com/2025/day/6) — we're halfway there! We've fallen through a garbage chute and landed in a trash compactor. A family of cephalopods offers to help us escape, but first we need to help their kid with math homework.
The worksheet has problems arranged in columns:
123 328 51 64
45 64 387 23
6 98 215 314
* + * +
Each column is a separate problem. The bottom row shows the operation. So 123 * 45 * 6 = 33210, 328 + 64 + 98 = 490, etc.
Parsing the Input (Round One)
The format is tricky — multiple spaces separate problems, single spaces might appear within numbers due to alignment. The operation line at the bottom gives us the column boundaries.
type Input = {
numbers: number[][];
operations: string[];
};
My first approach: split on whitespace, filter empty strings, parse numbers. It worked for part one:
const lines = readInputLines(day);
const opsLine = lines.pop()!;
const numbers = lines.map(line =>
line.split(" ")
.filter(token => token !== "")
.map(n => parseInt(n, 10))
);
const operations = opsLine.split(" ")
.filter(token => token !== "")
.map(op => op as "+" | "*");
Part One: Column Operations
With the input parsed, part one is straightforward. For each column, apply the operation to all numbers:
const sum = (nums: number[]) => nums.reduce((a, b) => a + b, 0);
const product = (nums: number[]) => nums.reduce((a, b) => a * b, 1);
const partOne = (input: Input, debug: boolean) => {
const { numbers, operations } = input;
let total = 0;
for (let index = 0; index < operations.length; index++) {
const op = operations[index];
const nums = numbers.map(row => row[index]);
const operation = op === "+" ? sum : product;
total += operation(nums);
}
return total;
};
Clean and simple. Too bad part two ruins everything.
Part Two: Cephalopod Math
Here's where it gets interesting. Cephalopods don't read numbers the way we do. They read right-to-left, top-to-bottom. Those aren't three separate numbers 64, 23, 314 in a column — they're the digits of numbers read vertically.
64
23
314
Reading top-to-bottom, right-to-left: 4, 3, 4 forms 434. Then 6, 2, 1 forms 621. Then just 3. So the numbers are 434, 621, and 3.
And here's the problem: my part one parsing destroyed this information. By splitting on whitespace, I lost track of which column each digit occupied.
Parsing the Input (Round Two)
Back to the drawing board. This time, preserve the exact character positions.
The key insight: the operations line tells us where each problem starts and ends. Each operation plus its trailing spaces defines a column width.
const processInput = (day: number): Input => {
const lines = readInputLines(day);
const opsLine = lines.pop()! + " "; // Add trailing space for consistency
// Use regex to find each operation with its spacing
const match = opsLine.match(/[+*]\s*/g)!;
const operations: string[] = [];
for (const op of match) {
operations.push(op.trim() as "+" | "*");
}
// Parse each line using the same column widths
const numbers = lines.map(line => {
const values = line + " ";
const nums: string[] = [];
let startIndex = 0;
for (const op of match) {
const chunk = values.slice(startIndex, startIndex + op.length);
nums.push(chunk);
startIndex += op.length;
}
return nums;
});
return { numbers, operations };
};
Now numbers is an array of string chunks that preserve spacing. For part one, just parse them as integers. For part two, we need to transpose and reconstruct.
Part Two: Matrix Transposition
Each "chunk" needs to be split into individual characters, then read column-by-column (transposed) to form the actual numbers:
const partTwo = (input: Input, debug: boolean) => {
const { numbers, operations } = input;
let total = 0;
for (let index = 0; index < operations.length; index++) {
const op = operations[index];
const chunks = numbers.map(row => row[index]);
// Split each chunk into characters
const chars = chunks.map(chunk => chunk.split(""));
// Transpose: read columns instead of rows
const maxLen = Math.max(...chars.map(c => c.length));
const reconstructed: number[] = [];
for (let col = 0; col < maxLen; col++) {
let numStr = "";
for (let row = 0; row < chars.length; row++) {
const char = chars[row][col];
if (char && char !== " ") {
numStr += char;
}
}
if (numStr) {
reconstructed.push(parseInt(numStr, 10));
}
}
const operation = op === "+" ? sum : product;
total += operation(reconstructed);
}
return total;
};
The transposition is the tricky part. We're flipping rows into columns — each vertical slice of digits becomes a number.
Performance
Both parts run in under 10 milliseconds. The input is wide (thousands of characters per line) but not deep, so the matrix operations are fast.
Takeaways
Don't destroy information during parsing: My part one parser worked great — until part two revealed I'd thrown away crucial positional data. When in doubt, preserve more than you think you need.
The operations line is the key: Using the operators and their spacing as a template for parsing the number rows was the breakthrough insight.
Matrix transposition is a common pattern: Reading columns instead of rows comes up frequently. The pattern
for (col) { for (row) { ... } }instead offor (row) { for (col) { ... } }is worth having in your toolkit.Test with the example first: The small input made the transposition logic much easier to debug. Switching between small and large inputs during development saved a lot of time.
Regex for structured text: The pattern
[+*]\s*elegantly captured each operation plus its trailing spaces, giving us exact column widths.
The full solution is available in the repository.