Skip to content

Commit f8467de

Browse files
committed
docs(2025): day 06 writeup
1 parent 612b6ea commit f8467de

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed

docs/2025/puzzles/day06.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,245 @@ import Solver from "../../../../../website/src/components/Solver.js"
22

33
# Day 6: Trash Compactor
44

5+
by [@scarf005](https://github.com/scarf005)
6+
57
## Puzzle description
68

79
https://adventofcode.com/2025/day/6
810

11+
## Solution Summary
12+
13+
Processing data row-by-row is usually straightforward, but handling columns can be tricky.
14+
Here comes [transpose](https://www.scala-lang.org/api/3.7.4/scala/collection/IterableOps.html#transpose-5d3) to the rescue!
15+
The `transpose` method switches the rows and columns of a 2D collection, which is exactly what we need for this puzzle.
16+
17+
## Part 1
18+
19+
```
20+
123 328 51 64
21+
45 64 387 23
22+
6 98 215 314
23+
* + * +
24+
```
25+
26+
To make working with columns easier, we'd like the inputs to be like this:
27+
28+
```
29+
123 45 6 *
30+
328 64 98 +
31+
51 387 215 *
32+
64 23 314 +
33+
```
34+
35+
First, let's split the input into a 2D grid:
36+
37+
```scala
38+
def part1(input: String) = input.linesIterator.toVector // split input into lines
39+
.map(_.trim.split(raw"\s+")) // split lines into words by whitespaces
40+
41+
// Vector(
42+
// Array(123, 328, 51, 64),
43+
// Array(45, 64, 387, 23),
44+
// Array(6, 98, 215, 314),
45+
// Array(*, +, *, +)
46+
// )
47+
```
48+
49+
After we transpose it, we get the desired output:
50+
51+
```scala
52+
def part1(input: String) = input.linesIterator.toVector
53+
.map(_.trim.split(raw"\s+"))
54+
.transpose
55+
56+
// Vector(
57+
// Vector(123, 45, 6, *),
58+
// Vector(328, 64, 98, +),
59+
// Vector(51, 387, 215, *),
60+
// Vector(64, 23, 314, +)
61+
// )
62+
```
63+
64+
Now it's a matter of processing each column, which is fairly straightforward.
65+
Let's define an [extension method](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html) to improve readability.
66+
67+
```scala
68+
extension (xs: IterableOnce[(symbol: String, nums: IterableOnce[String])])
69+
inline def calculate: Long = xs.iterator.collect {
70+
case ("*", nums) => nums.iterator.map(_.toLong).product
71+
case ("+", nums) => nums.iterator.map(_.toLong).sum
72+
}.sum
73+
```
74+
75+
Finally:
76+
77+
```scala
78+
def part1(input: String): Long = input.linesIterator.toVector
79+
.map(_.trim.split(raw"\s+"))
80+
.transpose
81+
.iterator
82+
.map { col => (col.last, col.init) }
83+
.calculate
84+
```
85+
86+
## Part 2
87+
88+
This time it's tricky, but what if we transpose the entire input string?
89+
90+
```
91+
123 328 51 64
92+
45 64 387 23
93+
6 98 215 314
94+
* + * +
95+
```
96+
97+
would become
98+
99+
```
100+
1 *
101+
24
102+
356
103+
104+
369+
105+
248
106+
8
107+
108+
32*
109+
581
110+
175
111+
112+
623+
113+
431
114+
4
115+
```
116+
117+
Which is exactly what we need to calculate cephalopod math!
118+
119+
```scala
120+
def part2(input: String) =
121+
val lines = input.linesIterator.toVector // get list of lines
122+
val ops = lines.last.split(raw"\s+").toVector // we'll use them later
123+
lines
124+
.init // transposing requires all rows to be of equal length, so remove symbols from last line for simplicity
125+
.transpose
126+
127+
// Vector(
128+
// Vector(1, , ),
129+
// Vector(2, 4, ),
130+
// Vector(3, 5, 6),
131+
// Vector( , , ),
132+
// Vector(3, 6, 9),
133+
// Vector(2, 4, 8),
134+
// Vector(8, , ),
135+
// Vector( , , ),
136+
// Vector( , 3, 2),
137+
// Vector(5, 8, 1),
138+
// Vector(1, 7, 5),
139+
// Vector( , , ),
140+
// Vector(6, 2, 3),
141+
// Vector(4, 3, 1),
142+
// Vector( , , 4)
143+
// )
144+
```
145+
146+
Now we can easily convert each column into cephalopod number strings:
147+
148+
```scala
149+
def part2(input: String) =
150+
val lines = input.linesIterator.toVector // get list of lines
151+
val ops = lines.last.split(raw"\s+").toVector // we'll use them later
152+
lines.init.transpose.map(_.mkString.trim)
153+
154+
// Vector(
155+
// "1",
156+
// "24",
157+
// "356",
158+
// "",
159+
// "369",
160+
// "248",
161+
// "8",
162+
// "",
163+
// "32",
164+
// "581",
165+
// "175",
166+
// "",
167+
// "623",
168+
// "431",
169+
// "4",
170+
// )
171+
```
172+
173+
The only thing left is to split this Vector by separator `""`. Sadly, the scala
174+
standard library doesn't have an `Iterable.splitBy` method (yet!), so we'll define our own:
175+
176+
```scala
177+
extension [A](xs: IterableOnce[A])
178+
// we're using Vector.newBuilder to build the result efficiently
179+
inline def splitBy(sep: A) =
180+
val (b, cur) = (Vector.newBuilder[Vector[A]], Vector.newBuilder[A]) // b stores the result, cur stores the current chunk
181+
for e <- xs.iterator do
182+
if e != sep then cur += e // if current element is not the separator, add it to the current chunk
183+
else { b += cur.result(); cur.clear() } // else, append the current chunk to result and clear it
184+
(b += cur.result()).result() // finally, append the last chunk and return the result
185+
```
186+
187+
```scala
188+
def part2(input: String) =
189+
val lines = input.linesIterator.toVector // get list of lines
190+
val ops = lines.last.split(raw"\s+").toVector // we'll use them later
191+
lines.init.transpose.map(_.mkString.trim).splitBy("")
192+
193+
// Vector(
194+
// Vector(1, 24, 356),
195+
// Vector(369, 248, 8),
196+
// Vector(32, 581, 175),
197+
// Vector(623, 431, 4)
198+
// )
199+
```
200+
201+
Reusing the `calculate` extension method from part 1, we can now finish part 2:
202+
203+
```scala
204+
def part2(input: String): Long =
205+
val lines = input.linesIterator.toVector
206+
val ops = lines.last.split(raw"\s+").toVector
207+
val xss = lines.init.transpose.map(_.mkString.trim).splitBy("")
208+
209+
(ops lazyZip xss) // zip the operations with the chunks, lazily for efficiency
210+
.calculate
211+
```
212+
213+
## Final Code
214+
215+
```scala
216+
extension [A](xs: IterableOnce[A])
217+
inline def splitBy(sep: A) =
218+
val (b, cur) = (Vector.newBuilder[Vector[A]], Vector.newBuilder[A])
219+
for e <- xs.iterator do
220+
if e != sep then cur += e else { b += cur.result(); cur.clear() }
221+
(b += cur.result()).result()
222+
223+
extension (xs: IterableOnce[(symbol: String, nums: IterableOnce[String])])
224+
inline def calculate: Long = xs.iterator.collect {
225+
case ("*", nums) => nums.iterator.map(_.toLong).product
226+
case ("+", nums) => nums.iterator.map(_.toLong).sum
227+
}.sum
228+
229+
def part1(input: String): Long = input.linesIterator.toVector
230+
.map(_.trim.split(raw"\s+"))
231+
.transpose
232+
.iterator
233+
.map { col => (col.last, col.view.init) }
234+
.calculate
235+
236+
def part2(input: String): Long =
237+
val lines = input.linesIterator.toVector
238+
val ops = lines.last.split(raw"\s+").toVector
239+
val xss = lines.init.transpose.map(_.mkString.trim).splitBy("")
240+
241+
(ops lazyZip xss).calculate
242+
```
243+
9244
## Solutions from the community
10245

11246
Share your solution to the Scala community by editing this page.

0 commit comments

Comments
 (0)