Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day 21 2024 #284

Merged
merged 1 commit into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/main/kotlin/me/peckb/aoc/_2024/calendar/day21/Day21.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package me.peckb.aoc._2024.calendar.day21

import javax.inject.Inject
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
import java.util.LinkedList
import kotlin.math.sign

class Day21 @Inject constructor(
private val generatorFactory: InputGeneratorFactory,
) {
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
solve(input, indirections = 3)
}

fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
solve(input, indirections = 26)
}

private fun solve(input: Sequence<String>, indirections: Int) : Long {
return input.sumOf { code ->
val length = getLength(NUMERIC_PAD, code, indirections)
val size = code.take(3).toLong()

length * size
}
}

private fun getLength(keyPad: Map<Char, Pair<Int, Int>>, code: String, indirections: Int): Long {
val lengthKey = LengthKey(keyPad.size, code, indirections)

if (LENGTH_CACHE.containsKey(lengthKey)) {
return LENGTH_CACHE[lengthKey]!!
}

if (indirections == 0) {
return code.length.toLong().also { LENGTH_CACHE[lengthKey] = it }
}

val minLength = "A${code}".toList()
.windowed(2)
.sumOf { (s, e) ->
paths(keyPad, s, e).minOf { getLength(DIRECTION_PAD, "${it}A", indirections - 1) }
}

return minLength.also { LENGTH_CACHE[lengthKey] = it }
}

private fun paths(keyPad: Map<Char, Pair<Int, Int>>, start: Char, end: Char): List<String> {
val pathKey = PathKey(keyPad.size, start, end)

if (PATHS_CACHE.containsKey(pathKey)) {
return PATHS_CACHE[pathKey]!!
}

val paths = mutableListOf<String>()
val partialPaths = LinkedList<Pair<Pair<Int, Int>, String>>().apply {
add(keyPad[start]!! to "")
}

pathGeneration@ while(partialPaths.isNotEmpty()) {
val (position, path) = partialPaths.poll()

val target = keyPad[end]!!
if (position == target) {
paths.add(path)
continue@pathGeneration
}

val yDelta = target.second - position.second
if (yDelta != 0) {
val newPoint = position.first to position.second + yDelta.sign
if (newPoint in keyPad.values) {
val dir = if (yDelta > 0) { ">" } else { "<" }
partialPaths.add(newPoint to "$path$dir")
}
}

val xDelta = target.first - position.first
if (xDelta != 0) {
val newPoint = position.first + xDelta.sign to position.second
if (newPoint in keyPad.values) {
val dir = if (xDelta > 0) { "v" } else { "^" }
partialPaths.add(newPoint to "$path$dir")
}
}
}

return paths.also { PATHS_CACHE[pathKey] = it }
}

companion object {
val NUMERIC_PAD = mapOf(
'7' to (0 to 0), '8' to (0 to 1), '9' to (0 to 2),
'4' to (1 to 0), '5' to (1 to 1), '6' to (1 to 2),
'1' to (2 to 0), '2' to (2 to 1), '3' to (2 to 2),
'0' to (3 to 1), 'A' to (3 to 2),
)

val DIRECTION_PAD = mapOf(
'^' to (0 to 1), 'A' to (0 to 2),
'<' to (1 to 0), 'v' to (1 to 1), '>' to (1 to 2)
)

val LENGTH_CACHE = mutableMapOf<LengthKey, Long>()

val PATHS_CACHE = mutableMapOf<PathKey, List<String>>()
}
}

data class LengthKey(val padIndicator: Int, val pattern: String, val robots: Int)

data class PathKey(val padIndicator: Int, val start: Char, val end: Char)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## [Day 21: Keypad Conundrum](https://adventofcode.com/2024/day/21)
2 changes: 2 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import me.peckb.aoc._2024.calendar.day17.Day17Test
import me.peckb.aoc._2024.calendar.day18.Day18Test
import me.peckb.aoc._2024.calendar.day19.Day19Test
import me.peckb.aoc._2024.calendar.day20.Day20Test
import me.peckb.aoc._2024.calendar.day21.Day21Test
import javax.inject.Singleton
import me.peckb.aoc.DayComponent
import me.peckb.aoc.InputModule
Expand Down Expand Up @@ -48,4 +49,5 @@ internal interface TestDayComponent : DayComponent {
fun inject(day18Test: Day18Test)
fun inject(day19Test: Day19Test)
fun inject(day20Test: Day20Test)
fun inject(day21Test: Day21Test)
}
32 changes: 32 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2024/calendar/day21/Day21Test.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package me.peckb.aoc._2024.calendar.day21

import javax.inject.Inject

import me.peckb.aoc._2024.DaggerTestDayComponent
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

internal class Day21Test {
@Inject
lateinit var day21: Day21

@BeforeEach
fun setup() {
DaggerTestDayComponent.create().inject(this)
}

@Test
fun testDay21PartOne() {
assertEquals(246_990, day21.partOne(DAY_21))
}

@Test
fun testDay21PartTwo() {
assertEquals(306_335_137_543_664, day21.partTwo(DAY_21))
}

companion object {
private const val DAY_21: String = "advent-of-code-input/2024/day21.input"
}
}
Loading