Skip to content

Commit f401656

Browse files
committed
Day 21
1 parent a7a6ec0 commit f401656

File tree

3 files changed

+158
-0
lines changed

3 files changed

+158
-0
lines changed

src/main/kotlin/common/datastructures/Grid.kt

+17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ fun Point.toDir() = Dir(row, col)
1414
val directions: List<Dir> = (0..3).map { i -> Dir(neighsY[i], neighsX[i]) }
1515
val directionsWithDiagonals: List<Dir> = neighsYDiags.zip(neighsXDiags) { y, x -> Dir(y, x) }
1616

17+
fun Dir.toChar() = when (this) {
18+
Dir(-1, 0) -> '^'
19+
Dir(0, 1) -> '>'
20+
Dir(1, 0) -> 'v'
21+
Dir(0, -1) -> '<'
22+
else -> error("invalid dir")
23+
}
24+
1725
class Grid<T>(val rows: List<List<Cell<T>>>) {
1826
constructor(rows: Int, cols: Int, constructor: (row: Int, col: Int) -> T) : this(Array(rows) { row ->
1927
Array(cols) { col ->
@@ -25,6 +33,15 @@ class Grid<T>(val rows: List<List<Cell<T>>>) {
2533
Cell<T>(row, col, constr(row, col, input[row][col]))
2634
}.toList()
2735
}.toList())
36+
companion object {
37+
fun <T> fromValues(rows: List<List<T>>): Grid<T> {
38+
return Grid(rows.mapIndexed { rowId, row ->
39+
row.mapIndexed { col, value ->
40+
Cell(rowId, col, value)
41+
}
42+
})
43+
}
44+
}
2845

2946
operator fun get(row: Int) = rows[row]
3047
operator fun get(p: Point) = rows[p.row][p.col]

src/main/kotlin/y24/Day21.kt

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package y24
2+
3+
import common.puzzle.solvePuzzle
4+
import common.puzzle.Input
5+
import common.puzzle.Puzzle
6+
import common.datastructures.*
7+
8+
9+
fun main() = solvePuzzle(year = 2024, day = 21) { Day21(it) }
10+
11+
typealias PressMap = Map<Pair<Char, Char>, List<String>>
12+
13+
class Day21(val input: Input) : Puzzle {
14+
private val numpad = Grid(listOf(
15+
"789",
16+
"456",
17+
"123",
18+
" 0A",
19+
)) { _, _, c -> c }
20+
private val keypad = Grid(listOf(
21+
" ^A",
22+
"<v>",
23+
)) { _, _, c -> c }
24+
25+
data class CacheKey(
26+
val from: Char,
27+
val to: Char,
28+
val robots: Int,
29+
)
30+
private val cache = mutableMapOf<CacheKey, Long>()
31+
32+
private val numpadPresses: PressMap = createNumpadPresses(numpad)
33+
private val keypadPresses: PressMap = createNumpadPresses(keypad)
34+
35+
private fun createNumpadPresses(grid: Grid<Char>): PressMap {
36+
val result = mutableMapOf<Pair<Char, Char>, List<String>>()
37+
grid.cells().forEach { c1 ->
38+
grid.cells().forEach { c2 ->
39+
result[c1.value to c2.value] = createPressesBetween(grid, c1.toPoint(), c2.toPoint(), mutableSetOf())
40+
}
41+
}
42+
43+
return result
44+
}
45+
46+
private fun createPressesBetween(grid: Grid<Char>, from: Point, to: Point, visited: MutableSet<Char>): List<String> {
47+
if (from == to) {
48+
return listOf("A")
49+
}
50+
51+
return directions.flatMap { dir ->
52+
val neigh = from + dir.toPoint()
53+
if (!grid.withinBounds(neigh.row, neigh.col)) {
54+
return@flatMap emptyList()
55+
}
56+
57+
val cell = grid[neigh]
58+
if (cell.value == ' ' || cell.value in visited) {
59+
return@flatMap emptyList()
60+
}
61+
62+
visited += cell.value
63+
return@flatMap createPressesBetween(grid, neigh, to, visited)
64+
.map { sequence -> dir.toChar() + sequence }
65+
.also {
66+
visited -= cell.value
67+
}
68+
}
69+
}
70+
71+
private fun keyPresses(pressMap: PressMap, key: CacheKey): Long {
72+
if (key in cache) {
73+
return cache.getValue(key)
74+
}
75+
76+
if (key.robots == 0) {
77+
return 1L
78+
}
79+
80+
return pressMap.getValue(key.from to key.to).minOf { pressSequence ->
81+
var c = 'A'
82+
pressSequence.sumOf { toPress ->
83+
keyPresses(keypadPresses, CacheKey(c, toPress, key.robots - 1)).also {
84+
c = toPress
85+
}
86+
}
87+
}.also {
88+
cache[key] = it
89+
}
90+
}
91+
92+
private fun sequenceMinPresses(seq: String, numRobots: Int): Long {
93+
var c = 'A'
94+
return seq.sumOf { toC ->
95+
keyPresses(numpadPresses, CacheKey(c, toC, numRobots + 1)).also {
96+
c = toC
97+
}
98+
}
99+
}
100+
101+
override fun solveLevel1(): Any {
102+
return input.lines.sumOf { seq ->
103+
val numeric = seq.filter { it.isDigit() }.toInt()
104+
numeric * sequenceMinPresses(seq, 2)
105+
}
106+
}
107+
108+
override fun solveLevel2(): Any {
109+
return input.lines.sumOf { seq ->
110+
val numeric = seq.filter { it.isDigit() }.toInt()
111+
numeric * sequenceMinPresses(seq, 25)
112+
}
113+
}
114+
}

src/test/kotlin/y24/Day21Test.kt

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package y24
2+
3+
import common.puzzle.Input
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
7+
internal class Day21Test {
8+
private val sample = Input("""
9+
029A
10+
980A
11+
179A
12+
456A
13+
379A
14+
""".trimIndent())
15+
16+
private val day = Day21(sample)
17+
18+
@Test
19+
fun solveLevel1() {
20+
assertEquals(126384L, day.solveLevel1())
21+
}
22+
23+
@Test
24+
fun solveLevel2() {
25+
assertEquals(154115708116294L, day.solveLevel2())
26+
}
27+
}

0 commit comments

Comments
 (0)