Advent of Code 2025 Day 4
The ease of Part 1 concerns me
I've solved this puzzle at least a dozen times throughout Advent of Code.
There is almost certainly going to be a unique twist in Part 2.
For now, it's time to solve this yet again.
Writing another adjacent grid cell inspector
First, I want to 'pad' the grid with a border of .s so I can safely check each original cell without having to account for out-of-bounds errors.
I usually do this after converting the string into a 2-dimensional array.
But this time, I will opt to create a new string by adding characters from a stringified array and replacing newline characters with dot-padded newline characters.
Here's the single, long concatenation statement:
let lineLength = input.indexOf('\n')
input = new Array(lineLength + 2).fill('.').join('')
+ '\n.'
+ input.replaceAll('\n', '.\n.')
+ '.\n'
+ new Array(lineLength + 2).fill('.').join('')
It turns this:
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
Into this:
............
...@@.@@@@..
.@@@.@.@.@@.
.@@@@@.@.@@.
.@.@@@@..@..
.@@.@@@@.@@.
..@@@@@@@.@.
..@.@.@.@@@.
.@.@@@.@@@@.
..@@@@@@@@..
.@.@.@@@.@..
............
And it works like a charm for any grid.
Next, my usual dictionary of relative coordinates for each adjacent cell:
let adjacents = [
[-1,-1], [-1,0], [-1,1],
[0,-1], [0,1],
[1,-1], [1,0], [1,1]
]
Next, creating the grid, initializing the counter, and shell of the nested for loop:
let answer = 0
let grid = input.split('\n').map(el => el.split(''))
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < input[0].length; col++) {
}
}
Lastly, checking each adjacent cell and tallying up the rolls:
answer += grid[row][col] == '@'
&& adjacents
.map(
cell => grid[cell[0] + row][cell[1] + col] == '.' ? false : true
)
.filter(el => el == true).length < 4 ? 1 : 0
It is one statement that does the following:
- Increment
answerby1... - As long as the current cell contains a
@... - And the number of adjacent cells containing a
@is less than4 - Otherwise increment by
0
Testing it on the example input produces the correct answer, 13.
What about on my puzzle input?
...
I got the correct answer!
Nice!
And now...for Part 2's likely diabolical twist.
Part 2
What a delightful twist
It is essentally Part 1 repeated until no change in state is produced.
Even as large as my grid is, I feel like brute-forcing this will still generate an answer fairly quickly.
I could be wrong, but that doesn't change my approach to solve this part.
Building on Part 1
For Part 1, I processed a single iteration.
I now need a while loop to process as many iterations as it takes until no new rolls can be removed.
And I need to keep track of both the amount and location of rolls to be removed (a.k.a. turned into empty cells).
let changers = []
do {
for (let row = 1; row < grid.length - 1; row++) {
for (let col = 1; col < grid[0].length - 1; col++) {
changers.push( grid[row][col] == '@' && adjacents.map(cell => grid[cell[0] + row][cell[1] + col] == '.' ? false : true).filter(el => el == true).length < 4 ? [row,col] : null)
}
}
changers = changers.filter(el => el !== null)
console.log(changers)
} while (changers.length > 0)
The code above successfully catalogs all cell locations of rolls that should be removed.
At least...after 1 iteration.
Since it never mutates the grid or clears the array, it repeatedly adds the same set of cell locations to the same array.
Time to fix that.
let changers = []
let answer = 0
do {
answer += changers.length
changers.forEach(cell => {
grid[cell[0]][cell[1]] = '.'
})
changers = []
for (let row = 1; row < grid.length - 1; row++) {
for (let col = 1; col < grid[0].length - 1; col++) {
changers.push( grid[row][col] == '@' && adjacents.map(cell => grid[cell[0] + row][cell[1] + col] == '.' ? false : true).filter(el => el == true).length < 4 ? [row,col] : null)
}
}
changers = changers.filter(el => el !== null)
} while (changers.length > 0)
It was a simple addition of the forEach() loop early in the do-while loop.
I also added the answer variable to track the sum of removed rolls.
Running it on the example input generates the correct answer.
As always, fingers crossed it runs on my puzzle input...and generates the correct answer.
...
It worked!
Finished immediately and generated the correct answer!
Woohoo!!
That was a lot easer than I expected Part 2 to be.
It makes me worried for a much tougher Day 5.
Can't wait to find out!
Top comments (0)