Advent of Code - Day 4: Printing Department


2025-12-04.png

You ride the escalator down to the printing department. They’re clearly getting ready for Christmas; they have lots of large rolls of paper everywhere, and there’s even a massive printer in the corner (to handle the really big print jobs).

-- Day 4 - Advent of Code 2025

Solution in Java

Full source can be found: in GitHub

Day 4 of the Advent-style assignment takes us deep into the North Pole’s printing department. The challenge revolves around a grid filled with paper rolls, represented by @, and empty spaces, represented by .. Forklifts can only access certain rolls based on how crowded their surroundings are, and our job is to identify those rolls and then simulate their removal.

The provided Java solution tackles this problem in a clean and iterative way. Let’s walk through how it works, starting with part one and then building on that logic for part two.

Part One: Finding Accessible Rolls of Paper

In the first part of the assignment, the task is straightforward: given the full grid of paper rolls, count how many of them are accessible by a forklift. A roll is accessible if there are fewer than four other rolls in the eight surrounding positions.

The solution begins by reading the input into a CharGrid, which is a convenient abstraction for working with a two-dimensional grid of characters. Once the grid is loaded, the solver delegates the real work to a helper method that determines which rolls are valid.

Here is the entry point for part one:

1@Override
2public void part1() {
3    ValidRolls validRolls = determineValidRolls();
4    validator.part1(validRolls.count);
5}

This method calls determineValidRolls(), which returns both the number of accessible rolls and their locations. For part one, only the count matters, so that value is passed directly to the validator.

The heart of part one lies in the determineValidRolls method itself:

 1private ValidRolls determineValidRolls() {
 2    int rollCount = 0;
 3    List<Point> locations = new ArrayList<>();
 4
 5    VirtualGrid<Character> virtualGrid = grid.virtual(3, 3);
 6    for (int y = 0; y < grid.rows(); y++) {
 7        for (int x = 0; x < grid.cols(); x++) {
 8            virtualGrid.position(x, y);
 9            if (virtualGrid.at() == '@' && virtualGrid.count('@') <= 4) {
10                rollCount++;
11                locations.add(new Point(x, y));
12            }
13        }
14    }
15
16    return new ValidRolls(rollCount, locations);
17}

The key idea here is the use of a VirtualGrid. By creating a 3×3 virtual view centred on each position, the solution can easily inspect the eight surrounding cells plus the centre cell itself. For every coordinate in the grid, the code checks two conditions: the current cell must contain a roll (@), and the total number of rolls in the 3×3 area must be four or fewer.

When both conditions are met, the roll is considered accessible. The solution increments a counter and records the roll’s location. At the end of the scan, both the count and the list of positions are wrapped into a simple record called ValidRolls.

Part Two: Repeatedly Removing Accessible Rolls

Part two builds on the logic from part one but adds an important twist. Accessible rolls can now be removed from the grid, and once they are gone, new rolls may become accessible. This process repeats until no more rolls can be removed.

Instead of reinventing the logic, the solution smartly reuses determineValidRolls() and applies it iteratively. The part2 method shows how this loop works:

 1@Override
 2public void part2() {
 3    long validRolls = 0;
 4    ValidRolls validLocations = determineValidRolls();
 5    while (!validLocations.locations.isEmpty()) {
 6        validRolls += validLocations.count;
 7
 8        // remove the roll from the grid for the next iteration
 9        validLocations.locations.forEach(p -> grid.set(p, '.'));
10        validLocations = determineValidRolls();
11    }
12
13    validator.part2(validRolls);
14}

The method starts by finding all currently accessible rolls. As long as at least one roll can be accessed, the loop continues. Each iteration adds the number of accessible rolls to a running total, then removes those rolls from the grid by replacing @ with . at their locations.

Once the grid has been updated, the solver recalculates accessibility by calling determineValidRolls() again. This mirrors the example from the assignment description, where layers of rolls are gradually peeled away as space opens up around them.

The loop naturally stops when no accessible rolls remain, at which point the total number of removed rolls is passed to the validator. For the example in the description, this process results in 43 removed rolls, and for the real input it produces the final answer required by the puzzle.


See also