|  | 
|  | 1 | +package net.olegg.aoc.year2024.day21 | 
|  | 2 | + | 
|  | 3 | +import net.olegg.aoc.someday.SomeDay | 
|  | 4 | +import net.olegg.aoc.utils.Directions | 
|  | 5 | +import net.olegg.aoc.utils.Vector2D | 
|  | 6 | +import net.olegg.aoc.utils.find | 
|  | 7 | +import net.olegg.aoc.utils.get | 
|  | 8 | +import net.olegg.aoc.utils.permutations | 
|  | 9 | +import net.olegg.aoc.year2024.DayOf2024 | 
|  | 10 | + | 
|  | 11 | +/** | 
|  | 12 | + * See [Year 2024, Day 21](https://adventofcode.com/2024/day/21) | 
|  | 13 | + */ | 
|  | 14 | +object Day21 : DayOf2024(21) { | 
|  | 15 | + private val pattern = "(\\d+)".toRegex() | 
|  | 16 | + private val digits = """ | 
|  | 17 | + 789 | 
|  | 18 | + 456 | 
|  | 19 | + 123 | 
|  | 20 | + 0A | 
|  | 21 | + """.trimIndent().lines().map { it.toList() } | 
|  | 22 | + private val arms = """ | 
|  | 23 | + ^A | 
|  | 24 | + <v> | 
|  | 25 | + """.trimIndent().lines().map { it.toList() } | 
|  | 26 | + | 
|  | 27 | + override fun first(): Any? { | 
|  | 28 | + val digitsMap = digits.flatMapIndexed { y, row -> | 
|  | 29 | + row.mapIndexedNotNull { x, c -> | 
|  | 30 | + if (c != ' ') c to Vector2D(x, y) else null | 
|  | 31 | + } | 
|  | 32 | + }.toMap() | 
|  | 33 | + | 
|  | 34 | + return lines.sumOf { line -> | 
|  | 35 | + val code = pattern.find(line)?.value?.toIntOrNull() ?: 0 | 
|  | 36 | + | 
|  | 37 | + val length = "A$line" | 
|  | 38 | + .zipWithNext { a, b -> bestPath(digits, arms, digitsMap[a]!!, digitsMap[b]!!, 0) } | 
|  | 39 | + .sum() | 
|  | 40 | + | 
|  | 41 | + code * length | 
|  | 42 | + } | 
|  | 43 | + } | 
|  | 44 | + | 
|  | 45 | + private val bestCache = mutableMapOf<Triple<Vector2D, Vector2D, Int>, Int>() | 
|  | 46 | + | 
|  | 47 | + private fun bestPath( | 
|  | 48 | + matrix: List<List<Char>>, | 
|  | 49 | + handler: List<List<Char>>, | 
|  | 50 | + from: Vector2D, | 
|  | 51 | + to: Vector2D, | 
|  | 52 | + level: Int, | 
|  | 53 | + ): Int { | 
|  | 54 | + val config = Triple(from, to, level) | 
|  | 55 | + return when { | 
|  | 56 | + level == 3 -> 1 | 
|  | 57 | + config in bestCache -> bestCache[config]!! | 
|  | 58 | + from == to -> 1 | 
|  | 59 | + else -> { | 
|  | 60 | + val delta = to - from | 
|  | 61 | + val basePath = buildList { | 
|  | 62 | + when { | 
|  | 63 | + delta.x > 0 -> repeat(delta.x) { add(Directions.R) } | 
|  | 64 | + delta.x < 0 -> repeat(-delta.x) { add(Directions.L) } | 
|  | 65 | + } | 
|  | 66 | + when { | 
|  | 67 | + delta.y > 0 -> repeat(delta.y) { add(Directions.D) } | 
|  | 68 | + delta.y < 0 -> repeat(-delta.y) { add(Directions.U) } | 
|  | 69 | + } | 
|  | 70 | + } | 
|  | 71 | + | 
|  | 72 | + val best = basePath.permutations() | 
|  | 73 | + .filter { path -> | 
|  | 74 | + path.scan(from) { curr, dir -> curr + dir.step }.none { matrix[it] == ' ' } | 
|  | 75 | + } | 
|  | 76 | + .minOf { path -> | 
|  | 77 | + val chars = buildList { | 
|  | 78 | + add('A') | 
|  | 79 | + path.forEach { | 
|  | 80 | + when (it) { | 
|  | 81 | + Directions.L -> add('<') | 
|  | 82 | + Directions.R -> add('>') | 
|  | 83 | + Directions.U -> add('^') | 
|  | 84 | + Directions.D -> add('v') | 
|  | 85 | + else -> Unit | 
|  | 86 | + } | 
|  | 87 | + } | 
|  | 88 | + add('A') | 
|  | 89 | + } | 
|  | 90 | + | 
|  | 91 | + chars.zipWithNext().sumOf { (fromChar, toChar) -> | 
|  | 92 | + val fromPoint = handler.find(fromChar)!! | 
|  | 93 | + val toPoint = handler.find(toChar)!! | 
|  | 94 | + bestPath(handler, handler, fromPoint, toPoint, level + 1) | 
|  | 95 | + } | 
|  | 96 | + } | 
|  | 97 | + | 
|  | 98 | + bestCache[config] = best | 
|  | 99 | + best | 
|  | 100 | + } | 
|  | 101 | + } | 
|  | 102 | + } | 
|  | 103 | +} | 
|  | 104 | + | 
|  | 105 | +fun main() = SomeDay.mainify(Day21) | 
0 commit comments