Day 7: Laboratories
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465


Kotlin
Part 1 is easily solved by simulating the beams and modifying the grid in-place.
This does not work for part 2, however. Here I opted for a BFS algorithm, moving down row-by-row.
Similar to other solutions, I store the amount of incoming paths to a splitter, so that I can reference it later. Practically, the two beams from each splitter are representatives of all incoming beams. This reduces the search complexity by a lot!
The final count of timelines is the sum of the array that collects the incoming beam counts for each bottom-row of the diagram.
Code on GitHub
Code
class Day07 : AOCSolution { override val year = 2025 override val day = 7 override fun part1(inputFile: String): String { val diagram = readResourceLines(inputFile) .mapArray { line -> line.mapArray { char -> Cell.byChar(char) } } .toGrid() var count = 0 for (y in 1 until diagram.height) { for (x in 0 until diagram.width) { // Search for beam sources in the preceding row if (diagram[x, y - 1] in Cell.beamSourceTypes) { // Simulate the beam moving down when (diagram[x, y]) { Cell.Empty -> diagram[x, y] = Cell.Beam Cell.Splitter -> { // Split the beam and count this splitter diagram[x - 1, y] = Cell.Beam diagram[x + 1, y] = Cell.Beam count++ } else -> continue } } } } return count.toString() } override fun part2(inputFile: String): String { val diagram = readResourceLines(inputFile) .mapArray { line -> line.mapArray { char -> Cell.byChar(char) } } .toGrid() val height = diagram.height.toLong() val startPosition = diagram.positionOfFirst { it == Cell.Start } // Working stack of beam origin and split origin positions val stack = ArrayDeque<Pair<Position, Position>>() stack.add(startPosition to startPosition) // Splitter positions mapped to the count of timelines to them // Start with the start position and 1 timeline. val splitters = mutableMapOf<Position, Long>(startPosition to 1) // Keep track of all splitters for which new beams have been spawned already // Could be used to solve part 1, as well val spawnedSplitters = mutableSetOf<Position>() // Count the timelines per diagram exit, which is the bottom-most row val diagramExits = LongArray(diagram.width) while (stack.isNotEmpty()) { // Breadth first search for memorizing the amount of paths to a splitter val (beamOrigin, splitOrigin) = stack.poll() val originPathCount = splitters.getValue(splitOrigin) val nextPosition = beamOrigin + Direction.DOWN if (nextPosition.y < height) { if (diagram[nextPosition] == Cell.Splitter) { if (nextPosition !in spawnedSplitters) { // Only spawn new beams, if they weren't spawned already stack.add((nextPosition + Direction.LEFT) to nextPosition) stack.add((nextPosition + Direction.RIGHT) to nextPosition) spawnedSplitters.add(nextPosition) // Initialize the count splitters[nextPosition] = originPathCount } else { splitters.computeIfPresent(nextPosition) { _, v -> v + originPathCount } } } else { // Just move down stack.add(nextPosition to splitOrigin) } } else { diagramExits[nextPosition.x.toInt()] += originPathCount } } // Sum the count of timelines leading to the bottom row, i.e. leaving the diagram for each position return diagramExits.sum().toString() } private companion object { enum class Cell(val char: Char) { Start('S'), Empty('.'), Splitter('^'), Beam('|'); override fun toString(): String { return char.toString() } companion object { fun byChar(char: Char) = entries.first { it.char == char } val beamSourceTypes = arrayOf(Start, Beam) } } } }