From 6cbed13ee4712d876b2fb841ef00dea7e02fd116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20R=C3=B8dland?= Date: Mon, 16 Dec 2024 21:03:28 +0100 Subject: [PATCH] 2024 - Day 16 - part 2 --- .../kotlin/no/rodland/advent_2024/Day16.kt | 68 +++++++++++++------ .../no/rodland/advent_2024/Day16Test.kt | 6 +- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/no/rodland/advent_2024/Day16.kt b/src/main/kotlin/no/rodland/advent_2024/Day16.kt index f73d6c6d..366f2a96 100644 --- a/src/main/kotlin/no/rodland/advent_2024/Day16.kt +++ b/src/main/kotlin/no/rodland/advent_2024/Day16.kt @@ -5,7 +5,7 @@ import java.util.* // template generated: 15/12/2024 // Fredrik Rødland 2024 -data class State(val pos: Pos, val dir: Direction, val cost: Int) +data class State(val pos: Pos, val dir: Direction, val cost: Int, val positions: Set) class Day16(val input: List) : Day { @@ -13,41 +13,71 @@ class Day16(val input: List) : Day { private val maze = parsed override fun partOne(): Int { - val shortestPath = dijkstra(maze) - return shortestPath + return dijkstraWithAllMinimalPaths(maze).first } override fun partTwo(): Int { - return 2 + return dijkstraWithAllMinimalPaths(maze).second.flatMap { it.positions }.toSet().size } - private fun dijkstra(maze: Cave): Int { + private fun dijkstraWithAllMinimalPaths(maze: Cave): Pair> { val start = Pos(1, maze.size - 2) // Starting position (S) val end = Pos(maze[0].size - 2, 1) // Ending position (E) val pq = PriorityQueue(compareBy { it.cost }) - val visited = mutableSetOf>() // Track visited states by Pos and Direction - - pq.add(State(start, Direction.EAST, 0)) + val costMap = mutableMapOf, Int>() + val endStates = mutableListOf() // Collect all end states + var minCost = Int.MAX_VALUE + pq.add(State(start, Direction.EAST, 0, setOf(start))) while (pq.isNotEmpty()) { val current = pq.poll() - if (!visited.add(Pair(current.pos, current.dir))) continue - if (current.pos == end) return current.cost - val move = current.pos.next(current.dir) - if (move in maze && maze[move] != '#') { - pq.add(State(move, current.dir, current.cost + 1)) + // If this state exceeds the known minimum cost, skip it + if (current.cost > minCost) continue + + // Allow revisiting if the cost is equal to the minimum + val key = current.pos to current.dir + + // if we have an entry in the cache for this (pos,dir) which is STRICTLY lower we don't have to + // keep adding it. If it's equal or null - we should handle it. + if (costMap[key]?.let { it < current.cost } == true) continue + costMap[key] = current.cost + + // Add positions to current state's path + val newPositions = current.positions + current.pos + + // If we reached the end, check and update the minimum cost + if (current.pos == end) { + minCost = current.cost + endStates.add(current.copy(positions = newPositions)) + continue + } + + // Generate next states + val nextPos = current.pos.next(current.dir) + val left = current.dir.left() + val nextLeft = current.pos.next(left) + val right = current.dir.right() + val nextRight = current.pos.next(right) + + if (inMazeAndNotWall(nextPos, maze)) { + pq.add(State(nextPos, current.dir, current.cost + 1, newPositions)) + } + if (inMazeAndNotWall(nextLeft, maze)) { + pq.add(State(nextLeft, left, current.cost + 1001, newPositions)) + } + if (inMazeAndNotWall(nextRight, maze)) { + pq.add(State(nextRight, right, current.cost + 1001, newPositions)) } - pq.add(State(current.pos, current.dir.left(), current.cost + 1000)) - pq.add(State(current.pos, current.dir.right(), current.cost + 1000)) } - return -1 // If no path is found + return minCost to endStates } - override fun List.parse(): Cave { - return this.toCave() - } + private fun inMazeAndNotWall(nextPos: Pos, maze: Cave) = nextPos in maze && maze[nextPos] != '#' + + + override fun List.parse() = this.toCave() override val day = "16".toInt() } diff --git a/src/test/kotlin/no/rodland/advent_2024/Day16Test.kt b/src/test/kotlin/no/rodland/advent_2024/Day16Test.kt index 7b37a733..1cb8fa63 100644 --- a/src/test/kotlin/no/rodland/advent_2024/Day16Test.kt +++ b/src/test/kotlin/no/rodland/advent_2024/Day16Test.kt @@ -16,9 +16,9 @@ internal class Day16Test { private val test16 = "2024/input_16_test.txt".readFile() private val resultTestOne = 7036 - private val resultTestTwo = 2 + private val resultTestTwo = 45 private val resultOne = 99460 - private val resultTwo = 2 + private val resultTwo = 500 val test = defaultTestSuiteParseOnInit( Day16(data16), @@ -64,6 +64,7 @@ internal class Day16Test { } @Test + @Slow(450) fun `16,1,live,1`() { report(test.livePart1) } @@ -77,6 +78,7 @@ internal class Day16Test { } @Test + @Slow(400) fun `16,2,live,1`() { report(test.livePart2) }