Skip to main content

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

Share your solution to the Scala community by editing this page. You can even write the whole article! Go here to volunteer