Advent of Code 2025 - Puzzle 6
Advent of Code is a yearly puzzle that runs through December where each day a new puzzle is released that gets progressively harder. The goal is to take the text input and create an algorithm in any language to solve the puzzle.
For this year I decided to make my solutions into interactive widgets on the web.
Spoilers
WARNING - Below is a spoiler. If you want to figure it out yourself have a look at the puzzle on Advent of Code and come back after you have solved it.
Spoilers below. Scroll down at your own risk.
.
.
.
The Puzzle
Puzzle 6 involves some basic spreadsheet maths. The input comes in the form of multiple rows and columns of numbers, and then a final row which says the operation that
needs to be performed for each column. The operations are set to be either addition + or multiplication *.
An example input is as follows:
1 2 3 4
5 6 7 8
9 10 11 12
+ * + *
One trick with this input is the variable number of spaces between each number. This means that when parsing the input we need to be careful to split by whitespace rather than a single space.
Solution
Parsing the input
Parsing the input is a little different for this puzzle due to the ordering when splitting by whitespace. In all previous puzzles the input has been grouped by rows, but in this case we need to group by columns.
Fortunately this is fairly straightforward using the row index and column index when iterating through the input.
For this puzzle I am looking to have an array of the values, and the operation for each column stored together in an object.
function parseInputToColumns(input) {
const rows = input.trim().split('\n').map(line => line.trim().split(/\s+/));
const columns = [];
for (let col = 0; col < rows[0].length; col++) {
// Default to addition, although it will be overwritten later
columns[col] = { values: [], operation: '+' };
for (let row = 0; row < rows.length - 1; row++) {
// It is easier to use numbers for calculations later
// so parse to a number here.
columns[col].values.push(Number(rows[row][col]));
}
}
// Parse each of the operations to their column
for (let col = 0; col < rows[0].length; col++) {
columns[col].operation = rows[rows.length - 1][col];
}
return columns;
}
Processing the columns
To process each column we can simply map each column based on the operation property. Depending on if it is addition or multiplication we can use the reduce function to calculate the total.
// Define the operations we want to support
const operationFunctions = {
'+': (values) => values.reduce((a, b) => a + b, 0),
'*': (values) => values.reduce((a, b) => a * b, 1),
};
function processColumns(columns) {
// Map each column to its calculated value
// There is an assumption here that the operation is always valid
return columns.map(column =>
operationFunctions[column.operation](column.values)
);
}
Another way
An interesting (and probably bad practice) way to do this is to use the eval function.
This is generally not recommended due to security concerns, but there are valid use cases for dynamic code execution.
In this case I would say it doesn’t really matter as it is just a puzzle solution that doesn’t execute anything other than local code, and it is an interesting way to solve the problem.
The idea is to join the values with the operation and then evaluate the resulting string as code.
function processColumnsEval(columns) {
return columns.map(column => {
const expression = column.values.join(` ${column.operation} `);
// Expression will be something like "1 + 5 + 9"
return eval(expression);
});
}
Showing the Result
This solution was used to make the interactive widget below. You can change the inputs to anything you like.
Part 2
The second part of this puzzle changes the concept of the input from left to right rows defining the values, to top to bottom characters defining the values. You can think of it as rotating the input 90 degrees counter-clockwise and using the new numbers that are formed.
This means the spaces from the initial input are now important as they define what the new numbers are.
For example, the number 1, 7 and 13 could result in any of the following:
- 171, 3
- 13, 71
- 11, 73
- Or more depending on how many spaces are allowed.
The widget below shows an example of those inputs rotating which results in option 3 - 11 and 73.
The actual input does not require an unknown number of spaces so the solution will assume that the largest number’s length per column is the total column width.
Solution
I think the easiest way to conceptualise this is to first read the inputs without the spaces, and then reread the inputs splitting by the known column widths. Using the existing parsing from part 1, the result is simply the maximum length of any of the numbers per column.
function getColumnSizes(input) {
// Use the same parsing as part 1 to get the columns without spaces
const columns = parseInputToColumns(input);
// The size of each column is the max length of each value in that column
return columns.map(column =>
Math.max(...column.values.map(value => value.toString().length))
);
}
Now that the size of each column is known, the columns can be re-parsed by reading each row and slicing the string based on the column sizes. This should result in values that include the spaces, which can then be used as blank values when rotated.
function parseInputRotated(input) {
const rows = input.trim().split('\n');
const columnSizes = getColumnSizes(input);
const columns = new Array(columnSizes.length)
.fill(null)
.map(() => ({ values: [], operation: '+' }));
// Read each row except the last one which contains the operations
for (let rowIndex = 0; rowIndex < rows.length - 1; rowIndex++) {
const row = rows[rowIndex];
let rowOffset = 0;
for (let columnIndex = 0; columnIndex < columnSizes.length; columnIndex++) {
// Current column size to take from the row string
const size = columnSizes[columnIndex];
// Column including spaces
const columnText = row.slice(rowOffset, rowOffset + size);
const column = columns[columnIndex];
column.values.push(columnText);
// Increment the offset for the next column (+1 for the space)
rowOffset = rowOffset + size + 1;
}
}
// Operations are only ever 1 character so we can just slice them directly
const operationsRow = rows[rows.length - 1];
const operations = operationsRow.trim().split(/\s+/);
for (let columnIndex = 0; columnIndex < columnSizes.length; columnIndex++) {
columns[columnIndex].operation = operations[columnIndex];
}
return columns;
}
Rotating the input
Now that the input strings are parsed the 90 degree rotation needs to be done. The rotation means that the last character of each row is joined to form the first row of the new input, and so on. The first row should be considered the most significant digit when forming the new numbers.
function rotateColumnsToNumbers(columns) {
for (const column of columns) {
column.parsed = [];
// parse the numbers in reverse
for (let i = column.values[0].length - 1; i >= 0; i--) {
let numberStr = '';
for (const value of column.values) {
// combine each number, trimming away any blank values
numberStr += value[i].trim();
}
const number = Number(numberStr);
column.parsed.push(number);
}
}
return columns;
}
/**
* Input: ["1 ", " 7", "13"]
* Output: [73, 11]
*/
Part 2 results
This solution was used to make the interactive widget below. You can change the inputs to anything you like.
Want to read more? Check out more posts below!