|
| 1 | +// Advent of Code 2024, Day 09. |
| 2 | +// By Sebastian Raaphorst, 2024. |
| 3 | + |
| 4 | +package day10 |
| 5 | + |
| 6 | +import common.aocreader.fetchAdventOfCodeInput |
| 7 | +import common.intpos2d.* |
| 8 | +import common.parsing.parseGrid |
| 9 | + |
| 10 | +private typealias Trail = List<IntPos2D> |
| 11 | +private typealias Trails = Set<List<IntPos2D>> |
| 12 | + |
| 13 | +private enum class Direction(val delta: IntPos2D) { |
| 14 | + NORTH(IntPos2D(-1, 0)), |
| 15 | + EAST(IntPos2D(0, 1)), |
| 16 | + SOUTH(IntPos2D(1, 0)), |
| 17 | + WEST(IntPos2D(0, -1)), |
| 18 | +} |
| 19 | + |
| 20 | +private fun parse(input: String): List<List<Int>> = |
| 21 | + input.trim().lines() |
| 22 | + .map { line -> line.trim().toList().map { it.digitToInt() } } |
| 23 | + |
| 24 | +private fun findTrailheads(grid: List<List<Int>>): Map<IntPos2D, Trails> { |
| 25 | + val height = grid.size |
| 26 | + val width = grid[0].size |
| 27 | + |
| 28 | + // Instead of going from zeros to nines, we go from nines to zeros |
| 29 | + // to count the number of trails to the peak. |
| 30 | + // Find the nines, i.e. the ending positions of the map. |
| 31 | + val nines = grid.flatMapIndexed { rowIdx, row -> |
| 32 | + row.mapIndexedNotNull { colIdx, height -> |
| 33 | + if (height == 9) IntPos2D(rowIdx, colIdx) else null |
| 34 | + } |
| 35 | + }.toSet() |
| 36 | + |
| 37 | + // We need to track the number of trails to each nine. |
| 38 | + val trails: MutableMap<IntPos2D, Trails> = mutableMapOf() |
| 39 | + |
| 40 | + // For each 9, we want to find the trails that lead to the peak. |
| 41 | + fun aux(trailSoFar: Trail): Trails { |
| 42 | + val currentPos = trailSoFar.last() |
| 43 | + val (currY, currX) = currentPos |
| 44 | + val currHeight = grid[currY][currX] |
| 45 | + if (currHeight == 0) return setOf(trailSoFar) |
| 46 | + |
| 47 | + // Try all the valid neighbours. |
| 48 | + val neighbours = Direction.entries |
| 49 | + .map { currentPos + it.delta } |
| 50 | + .filter { coords -> coords.first in 0 until height && coords.second in 0 until width } |
| 51 | + .filter { coords -> grid[coords.first][coords.second] == currHeight - 1 } |
| 52 | + if (neighbours.isEmpty()) return emptySet() |
| 53 | + |
| 54 | + return neighbours.flatMap { pos -> aux(trailSoFar + pos) }.toSet() |
| 55 | + } |
| 56 | + |
| 57 | + nines.forEach { nine -> |
| 58 | + val allTrails = aux(listOf(nine)) |
| 59 | + trails[nine] = allTrails |
| 60 | + } |
| 61 | + |
| 62 | + return trails |
| 63 | +} |
| 64 | + |
| 65 | +fun countTrailheads(trails: Map<IntPos2D, Trails>): Int = |
| 66 | + trails.values.flatten().toSet().size |
| 67 | + |
| 68 | +fun answer1(input: String): Int = |
| 69 | + parse(input).let(::findTrailheads).let(::countTrailheads) |
| 70 | + |
| 71 | +fun answer2(input: String): Int = |
| 72 | + TODO() |
| 73 | + |
| 74 | +fun main() { |
| 75 | + val input = fetchAdventOfCodeInput(2024, 10) |
| 76 | + |
| 77 | + println("--- Day 10: Resonant Collinearity ---") |
| 78 | + |
| 79 | + // Part 1: |
| 80 | + println("Part 1: ${answer1(input)}") |
| 81 | + |
| 82 | + // Part 2: |
| 83 | + println("Part 2: ${answer2(input)}") |
| 84 | +} |
0 commit comments