|
| 1 | +package org.adventofcode |
| 2 | + |
| 3 | +import java.io.File |
| 4 | + |
| 5 | +typealias EnergyLevel = Int; |
| 6 | +typealias EnergyGrid = List<List<EnergyLevel>>; |
| 7 | +typealias EnergyGridMap = Map<Point, EnergyLevel> |
| 8 | +data class Point(val row: Int = 0, val col: Int = 0) |
| 9 | +data class Simulation(val grid: EnergyGrid) |
| 10 | +data class FlashResult(val energyGridMap: EnergyGridMap, val flashedPoints: Set<Point>) |
| 11 | +data class SimulationStepResult(val simulation: Simulation, val flashedPoints: Set<Point>) { |
| 12 | + val grid = simulation.grid |
| 13 | +} |
| 14 | + |
| 15 | +fun EnergyLevel.step(): EnergyLevel { |
| 16 | + return when (this) { |
| 17 | + 9 -> 0 |
| 18 | + else -> this + 1 |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +fun Point.toMinimalString(): String { |
| 23 | + return "($row, $col)" |
| 24 | +} |
| 25 | + |
| 26 | +operator fun EnergyGrid.contains(p: Point): Boolean { |
| 27 | + if (p.row < 0 || p.row >= this.size) return false |
| 28 | + val rowData = this[p.row] |
| 29 | + if (p.col < 0 || p.col >= rowData.size) return false |
| 30 | + return true |
| 31 | +} |
| 32 | + |
| 33 | +fun EnergyGrid.findAllPointsNeighboring(p: Point): List<Point> { |
| 34 | + return (-1..1).map { rowOffset -> |
| 35 | + (-1..1).map { colOffset -> |
| 36 | + val neighboringPoint = Point(p.row + rowOffset, p.col + colOffset) |
| 37 | + if (neighboringPoint in this && neighboringPoint != p) { |
| 38 | + neighboringPoint |
| 39 | + } else null |
| 40 | + } |
| 41 | + }.flatten().filterNotNull() |
| 42 | +} |
| 43 | + |
| 44 | +fun EnergyGrid.map(fn: ((Point, EnergyLevel) -> EnergyLevel)? = null): EnergyGrid { |
| 45 | + return this.mapIndexed { rowIdx, rowData -> |
| 46 | + rowData.mapIndexed { colIdx, energyLevel -> |
| 47 | + val point = Point(rowIdx, colIdx) |
| 48 | + fn?.let { fn(point, energyLevel) } ?: energyLevel |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +fun EnergyGrid.toMap( |
| 54 | + fn: ((Point, EnergyLevel) -> EnergyLevel)? = null, |
| 55 | +): Map<Point, EnergyLevel> { |
| 56 | + return this.mapIndexed { rowIdx, rowData -> |
| 57 | + rowData.mapIndexed { colIdx, energyLevel -> |
| 58 | + val point = Point(rowIdx, colIdx) |
| 59 | + val nextEnergyLevel = fn?.let { fn(point, energyLevel) } ?: energyLevel |
| 60 | + point to nextEnergyLevel |
| 61 | + } |
| 62 | + }.flatten().toMap() |
| 63 | +} |
| 64 | + |
| 65 | +fun EnergyGrid.prettyPrint() { |
| 66 | + this.map { rowData -> |
| 67 | + println(rowData.joinToString(" ")) |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +fun EnergyGridMap.applyTo(energyGrid: EnergyGrid): EnergyGrid { |
| 72 | + return energyGrid.map { point, energy -> this[point].let { it } ?: energy } |
| 73 | +} |
| 74 | + |
| 75 | +fun Simulation.flashAt( |
| 76 | + focusPoint: Point, |
| 77 | + energyGridMap: EnergyGridMap, |
| 78 | + flashedPoints: Set<Point> = setOf(), |
| 79 | +): FlashResult { |
| 80 | + val focusEnergyLevel = energyGridMap[focusPoint] |
| 81 | + |
| 82 | + if (focusEnergyLevel != 0 || focusPoint in flashedPoints) { |
| 83 | + return FlashResult(energyGridMap, flashedPoints) |
| 84 | + } |
| 85 | + |
| 86 | + val neighboringPoints = grid.findAllPointsNeighboring(focusPoint) |
| 87 | + val updatedEnergyLevels = neighboringPoints.mapNotNull { point -> |
| 88 | + energyGridMap[point]?.let { energyLevel -> |
| 89 | + if (energyLevel > 0 && point !in flashedPoints) { |
| 90 | + point to energyLevel.step() |
| 91 | + } else null |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + val nextFlashedPoints = focusEnergyLevel?.let { if (it == 0) { |
| 96 | + flashedPoints + focusPoint |
| 97 | + } else flashedPoints } ?: flashedPoints |
| 98 | + val nextEnergyGridMap = energyGridMap + updatedEnergyLevels |
| 99 | + |
| 100 | + if (updatedEnergyLevels.isEmpty()) { |
| 101 | + return FlashResult(nextEnergyGridMap, nextFlashedPoints) |
| 102 | + } |
| 103 | + |
| 104 | + val result = updatedEnergyLevels |
| 105 | + .map { (p) -> p } |
| 106 | + .fold(nextEnergyGridMap to nextFlashedPoints) { acc, point -> |
| 107 | + val result = flashAt(point, acc.first, acc.second) |
| 108 | + acc.first + result.energyGridMap to acc.second + result.flashedPoints |
| 109 | + } |
| 110 | + |
| 111 | + return FlashResult(result.first, result.second) |
| 112 | +} |
| 113 | + |
| 114 | +fun Simulation.flashAt( |
| 115 | + row: Int, |
| 116 | + col: Int, |
| 117 | + energyGridMap: EnergyGridMap, |
| 118 | + flashedPoints: Set<Point> = setOf(), |
| 119 | +): FlashResult { |
| 120 | + return flashAt(Point(row, col), energyGridMap, flashedPoints) |
| 121 | +} |
| 122 | + |
| 123 | +fun Simulation.flashAll(energyGridMap: EnergyGridMap): FlashResult { |
| 124 | + val startingPoints = energyGridMap |
| 125 | + .toList() |
| 126 | + .filter { (_, energyLevel) -> energyLevel == 0 } |
| 127 | + .map { (p) -> p } |
| 128 | + val initFlashedPoint = setOf<Point>() |
| 129 | + val result = startingPoints |
| 130 | + .fold(energyGridMap to initFlashedPoint) { acc, point -> |
| 131 | + val result = flashAt(point, acc.first, acc.second) |
| 132 | + acc.first + result.energyGridMap to acc.second + result.flashedPoints |
| 133 | + } |
| 134 | + return FlashResult(result.first, result.second) |
| 135 | +} |
| 136 | + |
| 137 | +fun Simulation.step(): SimulationStepResult { |
| 138 | + val energyGridMap = this.grid.toMap { _, energyLevel -> energyLevel.step() } |
| 139 | + val flashResult = flashAll(energyGridMap) |
| 140 | + val nextGrid = flashResult.energyGridMap.applyTo(this.grid) |
| 141 | + return SimulationStepResult(Simulation(nextGrid), flashResult.flashedPoints) |
| 142 | +} |
| 143 | + |
| 144 | +fun runSoluationPart1(energyGrid: EnergyGrid, stepCount: Int = 10) { |
| 145 | + println("Day 11 Solution: Part1\n") |
| 146 | + |
| 147 | + var initSimulation = Simulation(energyGrid) |
| 148 | + val initStep = Triple(0, initSimulation, 0) |
| 149 | + var steps = (1..stepCount).fold(listOf(initStep)) { steps, stepCounter -> |
| 150 | + val (_, simulation, flashCount) = steps.last() |
| 151 | + val stepResult = simulation.step() |
| 152 | + steps + Triple(stepCounter, stepResult.simulation, stepResult.flashedPoints.size) |
| 153 | + } |
| 154 | + |
| 155 | + steps.forEach { (step, simulation, flashCount) -> |
| 156 | + println("After step $step") |
| 157 | + simulation.grid.prettyPrint() |
| 158 | + println() |
| 159 | + } |
| 160 | + |
| 161 | + val totalFlashes = steps.sumOf { (_, _, flashCount) -> flashCount } |
| 162 | + println("Total flashes: $totalFlashes") |
| 163 | +} |
| 164 | + |
| 165 | +fun main() { |
| 166 | + val energyGrid = File("day11/src/main/resources/puzzleInput.txt") |
| 167 | + .readLines() |
| 168 | + .map { it.toList().map { c -> c.digitToInt() } } |
| 169 | + |
| 170 | + runSoluationPart1(energyGrid, 100) |
| 171 | +} |
0 commit comments