You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/2025/puzzles/day07.md
+256Lines changed: 256 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,10 +2,265 @@ import Solver from "../../../../../website/src/components/Solver.js"
2
2
3
3
# Day 7: Laboratories
4
4
5
+
by [@aamiguet](https://github.com/aamiguet/)
6
+
5
7
## Puzzle description
6
8
7
9
https://adventofcode.com/2025/day/7
8
10
11
+
## Solution Summary
12
+
13
+
- Parse the input representing the faulty tachyon manifold into an `Array` of `String`.
14
+
- In part 1, we count the number of times a tachyon beam is split.
15
+
- In part 2, we count all the possible timelines (paths) a tachyon can take in the manifold.
16
+
17
+
## Parsing the input
18
+
19
+
Parsing the input is quite straighforward. First let's define a type alias so that we have a meaningful type name for our value:
20
+
21
+
```scala
22
+
typeManifold=Array[String]
23
+
```
24
+
25
+
As the input is a multiline `String` with each line representing a row of the manifold, we simply split it by lines:
26
+
27
+
```scala
28
+
privatedefparse(input: String):Manifold=
29
+
input.split("\n")
30
+
```
31
+
32
+
## Part 1
33
+
34
+
We have to count the number of times a beam is split. A split occurs when a beam hits a splitter `^` at position `i` . The beam is then split and continue at position `i - 1` and `i + 1` in the next row (line) of the manifold.
35
+
36
+
We process the manifold in the direction of the beam, top to bottom, row by row. For each row, we have to do two things:
37
+
38
+
- Count the number of splitters hit by a beam
39
+
- Update the positions of the beam for the next row
40
+
41
+
Let's first parse our manifold and find the initial position of the beam:
42
+
43
+
```scala
44
+
valmanifold= parse(input)
45
+
valbeamSource=Set(manifold.head.indexOf('S'))
46
+
```
47
+
48
+
We then iterate over all the remaining rows using `foldLeft`. Our initial value is composed of the `Set` containing the index of the source of the beam and an initial split count of 0.
49
+
50
+
At each step we update both the positions of the beam and the cumulative split count and finally return the final count.
We now have everything we need for the next step :
98
+
99
+
From the previous beam indices we compute the new beam indices `updatedBeamIndices`:
100
+
101
+
- Add the split beam indices : to the right and to the left of each split index.
102
+
- Remove the `splitIndices` as the beam is discontinued after a splitter.
103
+
104
+
```scala
105
+
valupdatedBeamIndices=
106
+
beamIndices ++ splitIndices.flatMap(i =>Set(i -1, i +1)) -- splitIndices
107
+
```
108
+
109
+
And update the cumulative split count, as `splitIndices` contains only the indices where a splitter is hit, it's simply:
110
+
111
+
```scala
112
+
splitCount + splitIndices.size
113
+
```
114
+
115
+
## Part 2
116
+
117
+
In part 2, we are tasked to count all the possible timelines (paths) a single tachyon can take in the manifold.
118
+
119
+
The problem in itself is not much different than part 1 but it has some pitfalls.
120
+
121
+
We could try to exhaustively compute all the possible paths and count them, but that would be time consuming as the manifold is quite big. Everytime a tachyon hits a splitter, the number of possible futures for this tachyon is doubled!
122
+
123
+
But we can actually count the number without knowing everything path. To do so we use the following property: all the tachyons reaching a given position `i` at a row `n` share the same future timelines. So we don't need to know their past timelines but only the number of tachyons for each position at each step.
124
+
125
+
Like in part 1, we parse the manifold and find the original position of the tachyon.
Once more we use `foldLeft` to iterate over the manifold. Our accumulator is now the `Map` counting the number of timelines for each tachyon position. Its initial value is the count of the single path the tachyon has taken from the source.
133
+
134
+
Finally we return the sum of all the timelines count.
List((i +1) -> pastTimelines, (i -1) -> pastTimelines)
146
+
.groupMap(_._1)(_._2)
147
+
.view
148
+
.mapValues(_.sum)
149
+
.toMap
150
+
valupdatedBeamTimelines=
151
+
splitTimelines
152
+
.foldLeft(beamTimelines): (bm, s) =>
153
+
bm.updatedWith(s._1):
154
+
caseNone=>Some(s._2)
155
+
caseSome(n) =>Some(n + s._2)
156
+
.removedAll(splitIndices)
157
+
updatedBeamTimelines
158
+
.values
159
+
.sum
160
+
```
161
+
162
+
Let's dive into it!
163
+
164
+
First, we reuse `findSplitIndices` from part 1 to find the splits.
165
+
166
+
Then we compute the new timelines originating from each split. Every time a tachyon hits a splitter two new timelines are created: one to the left and one to the right of the splitter. This doubles the number of timelines. Example:
167
+
168
+
>If a tachyon with 3 different past timelines hits a splitter at position `i`, in the next step we have two possible tachyons with each 3 different past timelines at position `i - 1` and `i + 1` making a total of 6 timelines.
169
+
170
+
Since we don't care about the past timelines but only the current positions: if multiple splits lead to the same tachyon position, we can group them and sum count of the past timelines which is done by applying `groupMap` and `mapValues` to the resulting `Map`.
171
+
172
+
Overall this is implemented with:
173
+
174
+
```scala
175
+
valsplitTimelines=
176
+
splitIndices
177
+
.flatMap: i =>
178
+
// splitting a timeline
179
+
valpastTimelines= beamTimelines(i)
180
+
List((i +1) -> pastTimelines, (i -1) -> pastTimelines)
181
+
// grouping and summing timelines by resulting position
182
+
.groupMap(_._1)(_._2)
183
+
.view
184
+
.mapValues(_.sum)
185
+
.toMap
186
+
```
187
+
188
+
From the previous beam timelines map we finally compute the new beam timelines `updatedBeamTimelines`:
189
+
190
+
- Merging the split timelines `Map`. By using `updateWith` we handle the two cases:
191
+
- If the entry already exists, we udpate it by adding the new timeline count to the existing one
192
+
- Or creating a new entry
193
+
- Removing all positions that hit a splitter
194
+
195
+
```scala
196
+
valupdatedBeamTimelines=
197
+
splitTimelines
198
+
.foldLeft(beamTimelines): (bm, s) =>
199
+
bm.updatedWith(s._1):
200
+
// adding a new key
201
+
caseNone=>Some(s._2)
202
+
// updating a value by summing both timeline counts
-[Solution](https://github.com/guycastle/advent_of_code/blob/main/src/main/scala/aoc2025/day07/DaySeven.scala) by [Guillaume Vandecasteele](https://github.com/guycastle)
16
271
-[Solution](https://github.com/YannMoisan/advent-of-code/blob/master/2025/src/main/scala/Day7.scala) by [Yann Moisan](https://github.com/YannMoisan)
17
272
-[Solution](https://github.com/Jannyboy11/AdventOfCode2025/blob/master/src/main/scala/day07/Day07.scala) by [Jan Boerman](https://x.com/JanBoerman95)
273
+
-[Solution](https://github.com/aamiguet/advent-2025/blob/main/src/main/scala/ch/aamiguet/advent2025/Day07.scala) by [Antoine Amiguet](https://github.com/aamiguet)
18
274
19
275
Share your solution to the Scala community by editing this page.
20
276
You can even write the whole article! [Go here to volunteer](https://github.com/scalacenter/scala-advent-of-code/discussions/842)
0 commit comments