Day 20 part 1.

This commit is contained in:
Sven Weidauer 2024-12-26 12:24:08 +01:00
parent aeb0d108ee
commit bba6a5b5b7
2 changed files with 39 additions and 52 deletions

View file

@ -1,7 +1,10 @@
import java.util.*
fun <T> dijkstra(start: T, goal: (T) -> Boolean, neighbors: (T) -> Sequence<Pair<T, Int>>): Int? {
data class PathResult<T>(val steps: List<Pair<T, Int>>, val totalDistance: Int)
fun <T> dijkstraPath(start: T, goal: (T) -> Boolean, neighbors: (T) -> Sequence<Pair<T, Int>>): PathResult<T>? {
val distanceFromStart = mutableMapOf(start to 0)
val previous = mutableMapOf<T, T>()
val visited = mutableSetOf<T>()
val queue = PriorityQueue<Pair<T, Int>> { a, b -> a.second.compareTo(b.second) }
@ -10,7 +13,15 @@ fun <T> dijkstra(start: T, goal: (T) -> Boolean, neighbors: (T) -> Sequence<Pair
while (true) {
val (current, totalDistance) = queue.poll() ?: break
if (goal(current)) {
return totalDistance
val path = generateSequence(current) { previous[it] }
.map { it to (distanceFromStart[it] ?: 0) }
.toList()
.reversed()
return PathResult(
path,
totalDistance
)
}
visited.add(start)
@ -23,10 +34,15 @@ fun <T> dijkstra(start: T, goal: (T) -> Boolean, neighbors: (T) -> Sequence<Pair
val currentDistance = distanceFromStart[neighbor]
if (currentDistance == null || newDistance < currentDistance) {
distanceFromStart[neighbor] = newDistance
previous[neighbor] = current
queue.add(neighbor to newDistance)
}
}
}
return null
}
fun <T> dijkstra(start: T, goal: (T) -> Boolean, neighbors: (T) -> Sequence<Pair<T, Int>>): Int? {
return dijkstraPath(start, goal, neighbors)?.totalDistance
}

View file

@ -1,58 +1,29 @@
fun main() {
val maze = CharGrid.read("day20-sample.txt")
val maze = CharGrid.read("day20.txt")
val start = maze.find('S') ?: error("No start position")
val end = maze.find('E') ?: error("No end position")
val paths = maze.findPaths(start, end)
val total = paths.first { !it.cheated }.time
val shortest = maze.findShortestPath(start, end) ?: error("No shortest path found")
val total = shortest.last().second
val toGoal = shortest.associate { (step, fromStart) ->
step to total - fromStart
}
println("Time without cheating: $total")
val part1 = paths.count { it.time <= total - 100 }
println("Part 1: $part1")
paths.sortedBy { it.time }
.groupBy { it.time }
.forEach { (time, list) ->
val saving = total - time
println("${list.count()} x $saving")
}
}
data class MazeResult(val time: Int, val cheated: Boolean)
fun CharGrid.findPaths(
position: Grid.Coordinate,
goal: Grid.Coordinate,
visited: Set<Grid.Coordinate> = emptySet(),
time: Int = 0,
cheated: Boolean = false
): List<MazeResult> {
if (position == goal) {
return listOf(MazeResult(time, cheated))
}
val newVisited = visited + position
fun canVisit(position: Grid.Coordinate): Boolean =
position in this && position !in newVisited && this[position] != '#'
val paths = position.neighbors()
.filter { canVisit(it) }
.fold(mutableListOf<MazeResult>()) { list, next ->
list.addAll(findPaths(next, goal, newVisited, time + 1, cheated))
list
}
return if (cheated) {
paths
} else {
position.neighbors()
.filter { it in this && this[it] == '#' }
val part1 = shortest.flatMap { (step, fromStart) ->
step.neighbors()
.filter { it in maze && maze[it] == '#' }
.flatMap { it.neighbors() }
.filter { canVisit(it) }
.fold(paths) { list, next ->
list.addAll(findPaths(next, goal, newVisited, time + 2, true))
list
}
}
.filter { it != step && it in maze && maze[it] != '#' }
.mapNotNull { toGoal[it] }
.map { fromStart + 2 + it }
}.count { it <= total - 100 }
println("Part 1: $part1")
}
fun CharGrid.findShortestPath(start: Grid.Coordinate, end: Grid.Coordinate) =
dijkstraPath(start, goal = { it == end }, neighbors = { position ->
position.neighbors()
.filter { it in this && this[it] != '#' }
.map { it to 1 }
})?.steps