diff --git a/src/main/kotlin/no/rodland/advent_2024/Day20.kt b/src/main/kotlin/no/rodland/advent_2024/Day20.kt index abd3f006..0db27a0c 100644 --- a/src/main/kotlin/no/rodland/advent_2024/Day20.kt +++ b/src/main/kotlin/no/rodland/advent_2024/Day20.kt @@ -1,26 +1,90 @@ package no.rodland.advent_2024 -import no.rodland.advent.Day +import no.rodland.advent.* // template generated: 20/12/2024 // Fredrik Rødland 2024 -class Day20(val input: List) : Day> { +class Day20(val input: List, private val limit: Int = 0, private val limitTestPart2: Int = limit) : Day, Pair>> { private val parsed = input.parse() + private val racetrack: Cave = parsed.first + private val start = parsed.second.first + private val end = parsed.second.second - override fun partOne(): Long { - return 2 + override fun partOne(): Int { + return solve(2, limit) } - override fun partTwo(): Long { - return 2 + override fun partTwo(): Int { + return solve(20, limitTestPart2) } - override fun List.parse(): List { - return map { line -> - line + private fun solve(maxDistance: Int, lim: Int): Int { + val shortestPath = bfs(start, end) + val size = shortestPath.size + val fromStart = shortestPath.mapIndexed { idx, pos -> pos to idx }.toMap() + val toEnd = shortestPath.mapIndexed { idx, pos -> pos to size - idx }.toMap() + return shortestPath.asSequence().flatMapIndexed() { idx, from -> + shortestPath + .subList(idx + 1, shortestPath.size) + .mapNotNull { to -> + val manhattanDistance = from.manhattanDistance(to) + if (manhattanDistance in 2..maxDistance) { + manhattanDistance to to + } else { + null + } + } + .map { to -> + size - (fromStart[from]!! + to.first + toEnd[to.second]!!) + }.filter { it > 0 } } + .groupBy { it } + .map { it.value.size to it.key } + .filter { it.second >= lim } + .sortedBy { it.second } + .sumOf { it.first } + } + + + private tailrec fun bfs( + start: Pos, + end: Pos, + queue: ArrayDeque> = ArrayDeque(listOf(listOf(start))), + visited: MutableSet = mutableSetOf(start) + ): List { + if (queue.isEmpty()) return emptyList() + // Dequeue the current path + val path = queue.removeFirst() + val current = path.last() + + // Check if we reached the end position + if (current == end) return path + current.neighbourCellsUDLR() + .filter { it !in visited } + .filter { it in racetrack } + .filter { racetrack[it] != '#' } + .forEach { neighbor -> + visited.add(neighbor) + queue.add(path + neighbor) // Enqueue the new path + } + // Recursive call with the updated queue and visited set + return bfs(start, end, queue, visited) + } + + override fun List.parse(): Pair, Pair> { + var start = Pos(0, 0) + var end = Pos(0, 0) + return this.mapIndexed { y, line -> + line.mapIndexed { x, c -> + when (c) { + 'S' -> start = Pos(x, y) + 'E' -> end = Pos(x, y) + } + c + }.toCharArray() + }.toTypedArray() to (start to end) } override val day = "20".toInt() diff --git a/src/test/kotlin/no/rodland/advent_2024/Day20Test.kt b/src/test/kotlin/no/rodland/advent_2024/Day20Test.kt index 88df1d18..6be29b52 100644 --- a/src/test/kotlin/no/rodland/advent_2024/Day20Test.kt +++ b/src/test/kotlin/no/rodland/advent_2024/Day20Test.kt @@ -15,20 +15,22 @@ internal class Day20Test { private val data20 = "2024/input_20.txt".readFile() private val test20 = "2024/input_20_test.txt".readFile() - private val resultTestOne = 2L - private val resultTestTwo = 2L - private val resultOne = 2L - private val resultTwo = 2L + private val resultTestOne = 44 + private val resultTestTwo = 285 + private val resultOne = 1502 + private val resultTwo = 1028136 val test = defaultTestSuiteParseOnInit( - Day20(data20), - Day20(test20), + Day20(data20, 100), + Day20(test20, 0, 50), resultTestOne, resultOne, resultTestTwo, resultTwo, - { Day20(data20) }, + { Day20(data20, 100) }, { Day20(test20) }, + numTestPart1 = 1, + numTestPart2 = 1, ) @Nested diff --git a/src/test/resources/2024/input_20_test.txt b/src/test/resources/2024/input_20_test.txt index e69de29b..f107d405 100644 --- a/src/test/resources/2024/input_20_test.txt +++ b/src/test/resources/2024/input_20_test.txt @@ -0,0 +1,15 @@ +############### +#...#...#.....# +#.#.#.#.#.###.# +#S#...#.#.#...# +#######.#.#.### +#######.#.#...# +#######.#.###.# +###..E#...#...# +###.#######.### +#...###...#...# +#.#####.#.###.# +#.#...#.#.#...# +#.#.#.#.#.#.### +#...#...#...### +############### \ No newline at end of file