Skip to content

Commit

Permalink
Day 20 2024 (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
peckb1 authored Dec 20, 2024
1 parent ae9fffd commit 0b278d9
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/main/kotlin/me/peckb/aoc/_2024/calendar/day16/Day16.kt
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ data class Room(
enum class Space { FULL, EMPTY }

enum class Direction(val yDelta: Int, val xDelta: Int) {
N(-1, 0), E(0, 1), S(1, 0), W(0, -1)
N(-1, 0),
E(0, 1),
S(1, 0),
W(0, -1)
}

private fun Direction?.turnCost(direction: Direction): Long {
Expand Down
108 changes: 108 additions & 0 deletions src/main/kotlin/me/peckb/aoc/_2024/calendar/day20/Day20.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package me.peckb.aoc._2024.calendar.day20

import javax.inject.Inject
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
import me.peckb.aoc.pathing.GenericIntDijkstra
import kotlin.math.abs

class Day20 @Inject constructor(
private val generatorFactory: InputGeneratorFactory,
) {
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
findCheats(input, 2)
}

fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
findCheats(input, 20)
}

private fun findCheats(input: Sequence<String>, maxCheatTime: Int): Int {
lateinit var end: Location

val maze = mutableListOf<MutableList<Space>>()
input.forEachIndexed { y, line ->
val row = mutableListOf<Space>()
line.forEachIndexed { x, c ->
when (c) {
'#' -> row.add(Space.FULL)
'.' -> row.add(Space.EMPTY)
'S' -> row.add(Space.EMPTY)
'E' -> row.add(Space.EMPTY.also { end = Location(y, x) } )
}
}
maze.add(row)
}

val solver = object : GenericIntDijkstra<Location>() {}

val costs = solver.solve(end.withArea(maze))
var cheats = 0

val explorationRange = (-maxCheatTime .. maxCheatTime)
val explorationDeltas = explorationRange.flatMapIndexed { y, yStep ->
explorationRange.mapIndexedNotNull { x, xStep ->
val stepCount = abs(yStep) + abs(xStep)
if (stepCount <= maxCheatTime) {
yStep to xStep
} else {
null
}
}
}

costs.entries.forEach { (cheatStart, _) ->
val reachableEmptySpaces = mutableSetOf<Location>()

val curY = cheatStart.y
val curX = cheatStart.x

explorationDeltas.forEach { (yStep, xStep) ->
val y = curY + yStep
val x = curX + xStep

if (y in maze.indices && x in maze[y].indices && maze[y][x] == Space.EMPTY) {
reachableEmptySpaces.add(Location(y, x))
}
}

reachableEmptySpaces.forEach { endSpace ->
val costAtCheatStart = costs[cheatStart]!!
val costAtCheatEnd = costs[endSpace]!!
val distanceTravelled = cheatStart.distanceFrom(endSpace)

val timeSaved = costAtCheatStart - costAtCheatEnd - distanceTravelled
if (timeSaved >= 100) { cheats++ }
}
}

return cheats
}
}

data class Location(val y: Int, val x: Int) : GenericIntDijkstra.DijkstraNode<Location> {
fun distanceFrom(other: Location): Int {
return abs(other.y - y) + abs(other.x - x)
}

lateinit var area: MutableList<MutableList<Space>>

fun withArea(area: MutableList<MutableList<Space>>) = apply { this.area = area }

override fun neighbors(): Map<Location, Int> {
return Direction.entries.mapNotNull { d ->
val (newY, newX) = d.yDelta + y to d.xDelta + x
if (newY in area.indices && newX in area[newY].indices && area[newY][newX] == Space.EMPTY) {
Location(newY, newX).withArea(area)
} else { null }
}.associateWith { 1 }
}
}

enum class Space { FULL, EMPTY }

enum class Direction(val yDelta: Int, val xDelta: Int) {
N(-1, 0),
E(0, 1),
S(1, 0),
W(0, -1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## [Day 20: Race Condition](https://adventofcode.com/2024/day/20)
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 @@ -19,6 +19,7 @@ import me.peckb.aoc._2024.calendar.day16.Day16Test
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 javax.inject.Singleton
import me.peckb.aoc.DayComponent
import me.peckb.aoc.InputModule
Expand Down Expand Up @@ -46,4 +47,5 @@ internal interface TestDayComponent : DayComponent {
fun inject(day17Test: Day17Test)
fun inject(day18Test: Day18Test)
fun inject(day19Test: Day19Test)
fun inject(day20Test: Day20Test)
}
32 changes: 32 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2024/calendar/day20/Day20Test.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package me.peckb.aoc._2024.calendar.day20

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 Day20Test {
@Inject
lateinit var day20: Day20

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

@Test
fun testDay20PartOne() {
assertEquals(1317, day20.partOne(DAY_20))
}

@Test
fun testDay20PartTwo() {
assertEquals(982474, day20.partTwo(DAY_20))
}

companion object {
private const val DAY_20: String = "advent-of-code-input/2024/day20.input"
}
}

0 comments on commit 0b278d9

Please sign in to comment.