Skip to content

Commit 97bd23a

Browse files
authored
Day 21 2024 (#284)
1 parent 0b278d9 commit 97bd23a

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package me.peckb.aoc._2024.calendar.day21
2+
3+
import javax.inject.Inject
4+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
5+
import java.util.LinkedList
6+
import kotlin.math.sign
7+
8+
class Day21 @Inject constructor(
9+
private val generatorFactory: InputGeneratorFactory,
10+
) {
11+
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
12+
solve(input, indirections = 3)
13+
}
14+
15+
fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
16+
solve(input, indirections = 26)
17+
}
18+
19+
private fun solve(input: Sequence<String>, indirections: Int) : Long {
20+
return input.sumOf { code ->
21+
val length = getLength(NUMERIC_PAD, code, indirections)
22+
val size = code.take(3).toLong()
23+
24+
length * size
25+
}
26+
}
27+
28+
private fun getLength(keyPad: Map<Char, Pair<Int, Int>>, code: String, indirections: Int): Long {
29+
val lengthKey = LengthKey(keyPad.size, code, indirections)
30+
31+
if (LENGTH_CACHE.containsKey(lengthKey)) {
32+
return LENGTH_CACHE[lengthKey]!!
33+
}
34+
35+
if (indirections == 0) {
36+
return code.length.toLong().also { LENGTH_CACHE[lengthKey] = it }
37+
}
38+
39+
val minLength = "A${code}".toList()
40+
.windowed(2)
41+
.sumOf { (s, e) ->
42+
paths(keyPad, s, e).minOf { getLength(DIRECTION_PAD, "${it}A", indirections - 1) }
43+
}
44+
45+
return minLength.also { LENGTH_CACHE[lengthKey] = it }
46+
}
47+
48+
private fun paths(keyPad: Map<Char, Pair<Int, Int>>, start: Char, end: Char): List<String> {
49+
val pathKey = PathKey(keyPad.size, start, end)
50+
51+
if (PATHS_CACHE.containsKey(pathKey)) {
52+
return PATHS_CACHE[pathKey]!!
53+
}
54+
55+
val paths = mutableListOf<String>()
56+
val partialPaths = LinkedList<Pair<Pair<Int, Int>, String>>().apply {
57+
add(keyPad[start]!! to "")
58+
}
59+
60+
pathGeneration@ while(partialPaths.isNotEmpty()) {
61+
val (position, path) = partialPaths.poll()
62+
63+
val target = keyPad[end]!!
64+
if (position == target) {
65+
paths.add(path)
66+
continue@pathGeneration
67+
}
68+
69+
val yDelta = target.second - position.second
70+
if (yDelta != 0) {
71+
val newPoint = position.first to position.second + yDelta.sign
72+
if (newPoint in keyPad.values) {
73+
val dir = if (yDelta > 0) { ">" } else { "<" }
74+
partialPaths.add(newPoint to "$path$dir")
75+
}
76+
}
77+
78+
val xDelta = target.first - position.first
79+
if (xDelta != 0) {
80+
val newPoint = position.first + xDelta.sign to position.second
81+
if (newPoint in keyPad.values) {
82+
val dir = if (xDelta > 0) { "v" } else { "^" }
83+
partialPaths.add(newPoint to "$path$dir")
84+
}
85+
}
86+
}
87+
88+
return paths.also { PATHS_CACHE[pathKey] = it }
89+
}
90+
91+
companion object {
92+
val NUMERIC_PAD = mapOf(
93+
'7' to (0 to 0), '8' to (0 to 1), '9' to (0 to 2),
94+
'4' to (1 to 0), '5' to (1 to 1), '6' to (1 to 2),
95+
'1' to (2 to 0), '2' to (2 to 1), '3' to (2 to 2),
96+
'0' to (3 to 1), 'A' to (3 to 2),
97+
)
98+
99+
val DIRECTION_PAD = mapOf(
100+
'^' to (0 to 1), 'A' to (0 to 2),
101+
'<' to (1 to 0), 'v' to (1 to 1), '>' to (1 to 2)
102+
)
103+
104+
val LENGTH_CACHE = mutableMapOf<LengthKey, Long>()
105+
106+
val PATHS_CACHE = mutableMapOf<PathKey, List<String>>()
107+
}
108+
}
109+
110+
data class LengthKey(val padIndicator: Int, val pattern: String, val robots: Int)
111+
112+
data class PathKey(val padIndicator: Int, val start: Char, val end: Char)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21)

src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import me.peckb.aoc._2024.calendar.day17.Day17Test
2020
import me.peckb.aoc._2024.calendar.day18.Day18Test
2121
import me.peckb.aoc._2024.calendar.day19.Day19Test
2222
import me.peckb.aoc._2024.calendar.day20.Day20Test
23+
import me.peckb.aoc._2024.calendar.day21.Day21Test
2324
import javax.inject.Singleton
2425
import me.peckb.aoc.DayComponent
2526
import me.peckb.aoc.InputModule
@@ -48,4 +49,5 @@ internal interface TestDayComponent : DayComponent {
4849
fun inject(day18Test: Day18Test)
4950
fun inject(day19Test: Day19Test)
5051
fun inject(day20Test: Day20Test)
52+
fun inject(day21Test: Day21Test)
5153
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2024.calendar.day21
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2024.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day21Test {
11+
@Inject
12+
lateinit var day21: Day21
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay21PartOne() {
21+
assertEquals(246_990, day21.partOne(DAY_21))
22+
}
23+
24+
@Test
25+
fun testDay21PartTwo() {
26+
assertEquals(306_335_137_543_664, day21.partTwo(DAY_21))
27+
}
28+
29+
companion object {
30+
private const val DAY_21: String = "advent-of-code-input/2024/day21.input"
31+
}
32+
}

0 commit comments

Comments
 (0)