Day 4: Printing Department
by @philippus
Puzzle description
https://adventofcode.com/2025/day/4
Solution Summary
- parse the input into a two-dimensional array representing the grid.
- for part 1, count the accessible rolls of paper by checking all the adjacent positions for all the rolls in the grid.
- for part 2, use the method created in part 1 repeatedly while updating the grid and keeping count until there are no more accessible rolls.
Part 1
First the input string needs to be parsed. A two-dimensional array of characters (Array[Array[Char]]) is used to
represent the grid. This makes it easy to reason about positions in the grid. And it also helps with speed, because we
can update the array. Split the input by the newline character, giving the rows in the grid. Then map each row, calling
toCharArray.
val grid: Array[Array[Char]] = input.split('\n').map(_.toCharArray)
It's nice to be able to visualize the grid, just like in the puzzle description. This can be done like this:
def drawGrid(grid: Array[Array[Char]]): String =
grid.map(_.mkString).mkString("\n") :+ '\n'
Calling println(drawGrid) on the sample input would give the following:
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
This can also be helpful to figure out subtle bugs in the solution.
A helper method countAdjacentRolls is created that counts the rolls of paper (@) in the 8 (or less, because of the
edges of the grid) adjacent positions in the grid for a given position.
def countAdjacentRolls(grid: Array[Array[Char]], pos: (x: Int, y: Int)): Int =
val adjacentRolls =
for
cy <- pos.y - 1 to pos.y + 1
cx <- pos.x - 1 to pos.x + 1
if (cx, cy) != (pos.x, pos.y) // exclude given position
if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
candidate = grid(cy)(cx)
if candidate == '@'
yield
candidate
adjacentRolls.length
To count all the accessible rolls of paper, all the positions in the grid containing a roll (@) should be checked for
the amount of adjacent rolls. Using calls to the indices method of the array the positions are generated. If a
position contains a roll and the amount of adjacent rolls for that position is less than 4 it gets counted towards the
total sum. The countAccessibleRolls method looks like this:
def countAccessibleRolls(grid: Array[Array[Char]]): Int =
(for
y <- grid.indices
x <- grid(y).indices
yield if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4 then 1 else 0).sum
This already gives the correct result, but it can be made a bit nicer by also updating the grid with xs and showing
the result:
def countAccessibleRollsAndUpdateGrid(grid: Array[Array[Char]]): Int =
var count = 0
for
y <- grid.indices
x <- grid(y).indices
if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4
do
count += 1
grid(y)(x) = 'x'
count
Since the grid is now updated during the loop with xs, the countAdjacentRolls needs an extra (|| candidate == 'x') condition, the
updated method looks like this:
def countAdjacentRolls(grid: Array[Array[Char]], pos: (x: Int, y: Int)): Int =
val adjacentRolls =
for
cy <- pos.y - 1 to pos.y + 1
cx <- pos.x - 1 to pos.x + 1
if (cx, cy) != (pos.x, pos.y) // exclude given position
if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
candidate = grid(cy)(cx)
if candidate == '@' || candidate == 'x'
yield
candidate
adjacentRolls.length
Calling println(drawGrid) after calling countAccessibleRollsAndUpdateGrid(grid) gives:
..xx.xx@x.
x@@.@.@.@@
@@@@@.x.@@
@.@@@@..@.
x@.@@@@.@x
.@@@@@@@.@
.@.@.@.@@@
x.@@@.@@@@
.@@@@@@@@.
x.x.@@@.x.
neat!
Part 2
To count all the removable rolls of paper the countAccessibleRollsAndUpdateGrid method can be used repeatedly in a
while loop, making sure that after each iteration, all the xs in the grid are replaced with a .. The complete
countRemovableRolls method looks like this:
def countRemovableRolls(grid: Array[Array[Char]]): Int =
var count = 0
var done = false
while !done do
val accessible = countAccessibleRollsAndUpdateGrid(grid)
if accessible == 0 then
done = true
else
count += accessible
for
y <- grid.indices
x <- grid(y).indices
if grid(y)(x) == 'x'
do
grid(y)(x) = '.'
count
Calling println(drawGrid) after calling countRemovableRolls(grid) gives:
..........
..........
..........
....@@....
...@@@@...
...@@@@@..
...@.@.@@.
...@@.@@@.
...@@@@@..
....@@@...
again exactly the same as in the puzzle description!
Final Code
def part1(input: String): Long =
val grid: Array[Array[Char]] = input.split('\n').map(_.toCharArray)
countAccessibleRolls(grid)
def part2(input: String): Long =
val grid: Array[Array[Char]] = input.split('\n').map(_.toCharArray)
countRemovableRolls(grid)
def countAdjacentRolls(grid: Array[Array[Char]], pos: (x: Int, y: Int)): Int =
val adjacentRolls =
for
cy <- pos.y - 1 to pos.y + 1
cx <- pos.x - 1 to pos.x + 1
if (cx, cy) != (pos.x, pos.y) // exclude given position
if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
candidate = grid(cy)(cx)
if candidate == '@' || candidate == 'x'
yield
candidate
adjacentRolls.length
def countAccessibleRolls(grid: Array[Array[Char]]): Int =
(for
y <- grid.indices
x <- grid(y).indices
yield if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4 then 1 else 0).sum
def countAccessibleRollsAndUpdateGrid(grid: Array[Array[Char]]): Int =
var count = 0
for
y <- grid.indices
x <- grid(y).indices
if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4
do
count += 1
grid(y)(x) = 'x'
count
def countRemovableRolls(grid: Array[Array[Char]]): Int =
var count = 0
var done = false
while !done do
val accessible = countAccessibleRollsAndUpdateGrid(grid)
if accessible == 0 then
done = true
else
count += accessible
for
y <- grid.indices
x <- grid(y).indices
if grid(y)(x) == 'x'
do
grid(y)(x) = '.'
count
def drawGrid(grid: Array[Array[Char]]): String =
grid.map(_.mkString).mkString("\n") :+ '\n'
Solutions from the community
- Solution by nichobi
- Solution by Guillaume Vandecasteele
- Solution by Philippus Baalman
- Solution by Antoine Amiguet
- Writeup by Bulby
- Solution by Yann Moisan
- Solution by Raphaël Marbeck
- Solution by merlinorg
- Solution by John Duffell
- Solution by Jan Boerman
- Solution by counter2015
- Solution by Paweł Cembaluk
Share your solution to the Scala community by editing this page. You can even write the whole article! Go here to volunteer