@@ -2,10 +2,240 @@ import Solver from "../../../../../website/src/components/Solver.js"
22
33# Day 4: Printing Department
44
5+ by [ @philippus ] ( https://github.com/philippus )
6+
57## Puzzle description
68
79https://adventofcode.com/2025/day/4
810
11+ ## Solution Summary
12+
13+ - parse the input into a two-dimensional array representing the grid.
14+ - for part 1, count the accessible rolls of paper by checking all the adjacent positions for all the rolls in the grid.
15+ - for part 2, use the method created in part 1 repeatedly while updating the grid and keeping count until there are no
16+ more accessible rolls.
17+
18+ ### Part 1
19+
20+ First the input string needs to be parsed. A two-dimensional array of characters (` Array[Array[Char]] ` ) is used to
21+ represent the grid. This makes it easy to reason about positions in the grid. And it also helps with speed, because we
22+ can update the array. Split the input by the newline character, giving the rows in the grid. Than map each row, calling
23+ ` toCharArray ` .
24+
25+ ``` scala
26+ val grid : Array [Array [Char ]] = input.split('\n ' ).map(_.toCharArray)
27+ ```
28+
29+ It's nice to be able to visualize the grid, just like in the puzzle description. This can be done like this:
30+
31+ ``` scala
32+ def drawGrid (grid : Array [Array [Char ]]): String =
33+ grid.map(_.mkString).mkString(" \n " ) :+ '\n '
34+ ```
35+
36+ Calling ` println(drawGrid) ` on the sample input would give the following:
37+
38+ ```
39+ ..@@.@@@@.
40+ @@@.@.@.@@
41+ @@@@@.@.@@
42+ @.@@@@..@.
43+ @@.@@@@.@@
44+ .@@@@@@@.@
45+ .@.@.@.@@@
46+ @.@@@.@@@@
47+ .@@@@@@@@.
48+ @.@.@@@.@.
49+ ```
50+
51+ This can also be helpful to figure out subtle bugs in the solution.
52+
53+ A helper method ` countAdjacentRolls ` is created that counts the rolls of paper (@) in the 8 (or less, because of the
54+ edges of the grid) adjacent positions in the grid for a given position.
55+
56+ ``` scala
57+ def countAdjacentRolls (grid : Array [Array [Char ]], pos : (x : Int , y : Int )): Int =
58+ val adjacentRolls =
59+ for
60+ cy <- pos.y - 1 to pos.y + 1
61+ cx <- pos.x - 1 to pos.x + 1
62+ if (cx, cy) != (pos.x, pos.y) // exclude given position
63+ if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
64+ candidate = grid(cy)(cx)
65+ if candidate == '@'
66+ yield
67+ candidate
68+ adjacentRolls.length
69+ ```
70+
71+ To count all the accessible rolls of paper, all the positions in the grid containing a roll (@) should be checked for
72+ the amount of adjacent rolls. Using calls to the ` indices ` method of the array the positions are generated. If a
73+ position contains a roll and the amount of adjacent rolls for that position is less than 4 it gets counted towards the
74+ total sum. The ` countAccessibleRoll ` method looks like this:
75+
76+ ``` scala
77+ def countAccessibleRolls (grid : Array [Array [Char ]]): Int =
78+ (for
79+ y <- grid.indices
80+ x <- grid(y).indices
81+ yield if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4 then 1 else 0 ).sum
82+ ```
83+
84+ This already gives the correct result, but it can be made a bit nicer by also updating the grid with ` x ` s and showing
85+ the result:
86+
87+ ``` scala
88+ def countAccessibleRollsAndUpdateGrid (grid : Array [Array [Char ]]): Int =
89+ var count = 0
90+ for
91+ y <- grid.indices
92+ x <- grid(y).indices
93+ if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4
94+ do
95+ count += 1
96+ grid(y)(x) = 'x'
97+ count
98+ ```
99+
100+ Since the grid is now updated during the loop with ` x ` s, the ` countAdjacentRolls ` needs an extra (` || candidate == 'x' ` ) condition, the
101+ updated method looks like this:
102+
103+ ``` scala
104+ def countAdjacentRolls (grid : Array [Array [Char ]], pos : (x : Int , y : Int )): Int =
105+ val adjacentRolls =
106+ for
107+ cy <- pos.y - 1 to pos.y + 1
108+ cx <- pos.x - 1 to pos.x + 1
109+ if (cx, cy) != (pos.x, pos.y) // exclude given position
110+ if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
111+ candidate = grid(cy)(cx)
112+ if candidate == '@' || candidate == 'x'
113+ yield
114+ candidate
115+ adjacentRolls.length
116+ ```
117+
118+ Calling ` println(drawGrid) ` after calling ` countAccessibleRollsAndUpdateGrid(grid) ` gives:
119+
120+ ```
121+ ..xx.xx@x.
122+ x@@.@.@.@@
123+ @@@@@.x.@@
124+ @.@@@@..@.
125+ x@.@@@@.@x
126+ .@@@@@@@.@
127+ .@.@.@.@@@
128+ x.@@@.@@@@
129+ .@@@@@@@@.
130+ x.x.@@@.x.
131+ ```
132+
133+ neat!
134+
135+ ### Part 2
136+
137+ To count all the removable rolls of paper the ` countAccessibleRollsAndUpdateGrid ` method can be used repeatedly in a
138+ while loop, making sure that after each iteration, all the ` x ` s in the grid are replaced with a ` . ` . The complete
139+ ` countRemovableRolls ` method looks like this:
140+
141+ ``` scala
142+ def countRemovableRolls (grid : Array [Array [Char ]]): Int =
143+ var count = 0
144+ var done = false
145+ while ! done do
146+ val accessible = countAccessibleRollsAndUpdateGrid(grid)
147+ if accessible == 0 then
148+ done = true
149+ else
150+ count += accessible
151+ for
152+ y <- grid.indices
153+ x <- grid(y).indices
154+ if grid(y)(x) == 'x'
155+ do
156+ grid(y)(x) = '.'
157+ count
158+ ```
159+
160+ Calling ` println(drawGrid) ` after calling ` countRemovableRolls(grid) ` gives:
161+
162+ ```
163+ ..........
164+ ..........
165+ ..........
166+ ....@@....
167+ ...@@@@...
168+ ...@@@@@..
169+ ...@.@.@@.
170+ ...@@.@@@.
171+ ...@@@@@..
172+ ....@@@...
173+ ```
174+
175+ again exactly the same as in the puzzle description!
176+
177+ ## Final Code
178+
179+ ``` scala
180+ def part1 (input : String ): Long =
181+ val grid : Array [Array [Char ]] = input.split('\n ' ).map(_.toCharArray)
182+ countAccessibleRolls(grid)
183+
184+ def part2 (input : String ): Long =
185+ val grid : Array [Array [Char ]] = input.split('\n ' ).map(_.toCharArray)
186+ countRemovableRolls(grid)
187+
188+ def countAdjacentRolls (grid : Array [Array [Char ]], pos : (x : Int , y : Int )): Int =
189+ val adjacentRolls =
190+ for
191+ cy <- pos.y - 1 to pos.y + 1
192+ cx <- pos.x - 1 to pos.x + 1
193+ if (cx, cy) != (pos.x, pos.y) // exclude given position
194+ if cy >= 0 && cy < grid.length && cx >= 0 && cx < grid(cy).length // exclude out of bounds positions
195+ candidate = grid(cy)(cx)
196+ if candidate == '@' || candidate == 'x'
197+ yield
198+ candidate
199+ adjacentRolls.length
200+
201+ def countAccessibleRolls (grid : Array [Array [Char ]]): Int =
202+ (for
203+ y <- grid.indices
204+ x <- grid(y).indices
205+ yield if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4 then 1 else 0 ).sum
206+
207+ def countAccessibleRollsAndUpdateGrid (grid : Array [Array [Char ]]): Int =
208+ var count = 0
209+ for
210+ y <- grid.indices
211+ x <- grid(y).indices
212+ if grid(y)(x) == '@' && countAdjacentRolls(grid, (x, y)) < 4
213+ do
214+ count += 1
215+ grid(y)(x) = 'x'
216+ count
217+
218+ def countRemovableRolls (grid : Array [Array [Char ]]): Int =
219+ var count = 0
220+ var done = false
221+ while ! done do
222+ val accessible = countAccessibleRollsAndUpdateGrid(grid)
223+ if accessible == 0 then
224+ done = true
225+ else
226+ count += accessible
227+ for
228+ y <- grid.indices
229+ x <- grid(y).indices
230+ if grid(y)(x) == 'x'
231+ do
232+ grid(y)(x) = '.'
233+ count
234+
235+ def drawGrid (grid : Array [Array [Char ]]): String =
236+ grid.map(_.mkString).mkString(" \n " ) :+ '\n '
237+ ```
238+
9239## Solutions from the community
10240
11241Share your solution to the Scala community by editing this page.
0 commit comments