Day 23.
This commit is contained in:
parent
bfee59402e
commit
12d2e88da1
241
src/aoc2023/Day23.kt
Normal file
241
src/aoc2023/Day23.kt
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package aoc2023
|
||||||
|
|
||||||
|
import CharGrid
|
||||||
|
import RelPos
|
||||||
|
import println
|
||||||
|
import readInput
|
||||||
|
|
||||||
|
/*
|
||||||
|
--- Day 23: A Long Walk ---
|
||||||
|
The Elves resume water filtering operations! Clean water starts flowing over the edge of Island Island.
|
||||||
|
They offer to help you go over the edge of Island Island, too! Just hold on tight to one end of this impossibly long rope and they'll lower you down a safe distance from the massive waterfall you just created.
|
||||||
|
As you finally reach Snow Island, you see that the water isn't really reaching the ground: it's being absorbed by the air itself. It looks like you'll finally have a little downtime while the moisture builds up to snow-producing levels. Snow Island is pretty scenic, even without any snow; why not take a walk?
|
||||||
|
There's a map of nearby hiking trails (your puzzle input) that indicates paths (.), forest (#), and steep slopes (^, >, v, and <).
|
||||||
|
For example:
|
||||||
|
#.#####################
|
||||||
|
#.......#########...###
|
||||||
|
#######.#########.#.###
|
||||||
|
###.....#.>.>.###.#.###
|
||||||
|
###v#####.#v#.###.#.###
|
||||||
|
###.>...#.#.#.....#...#
|
||||||
|
###v###.#.#.#########.#
|
||||||
|
###...#.#.#.......#...#
|
||||||
|
#####.#.#.#######.#.###
|
||||||
|
#.....#.#.#.......#...#
|
||||||
|
#.#####.#.#.#########v#
|
||||||
|
#.#...#...#...###...>.#
|
||||||
|
#.#.#v#######v###.###v#
|
||||||
|
#...#.>.#...>.>.#.###.#
|
||||||
|
#####v#.#.###v#.#.###.#
|
||||||
|
#.....#...#...#.#.#...#
|
||||||
|
#.#########.###.#.#.###
|
||||||
|
#...###...#...#...#.###
|
||||||
|
###.###.#.###v#####v###
|
||||||
|
#...#...#.#.>.>.#.>.###
|
||||||
|
#.###.###.#.###.#.#v###
|
||||||
|
#.....###...###...#...#
|
||||||
|
#####################.#
|
||||||
|
|
||||||
|
You're currently on the single path tile in the top row; your goal is to reach the single path tile in the bottom row. Because of all the mist from the waterfall, the slopes are probably quite icy; if you step onto a slope tile, your next step must be downhill (in the direction the arrow is pointing). To make sure you have the most scenic hike possible, never step onto the same tile twice. What is the longest hike you can take?
|
||||||
|
In the example above, the longest hike you can take is marked with O, and your starting position is marked S:
|
||||||
|
#S#####################
|
||||||
|
#OOOOOOO#########...###
|
||||||
|
#######O#########.#.###
|
||||||
|
###OOOOO#OOO>.###.#.###
|
||||||
|
###O#####O#O#.###.#.###
|
||||||
|
###OOOOO#O#O#.....#...#
|
||||||
|
###v###O#O#O#########.#
|
||||||
|
###...#O#O#OOOOOOO#...#
|
||||||
|
#####.#O#O#######O#.###
|
||||||
|
#.....#O#O#OOOOOOO#...#
|
||||||
|
#.#####O#O#O#########v#
|
||||||
|
#.#...#OOO#OOO###OOOOO#
|
||||||
|
#.#.#v#######O###O###O#
|
||||||
|
#...#.>.#...>OOO#O###O#
|
||||||
|
#####v#.#.###v#O#O###O#
|
||||||
|
#.....#...#...#O#O#OOO#
|
||||||
|
#.#########.###O#O#O###
|
||||||
|
#...###...#...#OOO#O###
|
||||||
|
###.###.#.###v#####O###
|
||||||
|
#...#...#.#.>.>.#.>O###
|
||||||
|
#.###.###.#.###.#.#O###
|
||||||
|
#.....###...###...#OOO#
|
||||||
|
#####################O#
|
||||||
|
|
||||||
|
This hike contains 94 steps. (The other possible hikes you could have taken were 90, 86, 82, 82, and 74 steps long.)
|
||||||
|
Find the longest hike you can take through the hiking trails listed on your map. How many steps long is the longest hike?
|
||||||
|
|
||||||
|
*/
|
||||||
|
fun main() {
|
||||||
|
|
||||||
|
val inlineTestInput = """
|
||||||
|
#.#####################
|
||||||
|
#.......#########...###
|
||||||
|
#######.#########.#.###
|
||||||
|
###.....#.>.>.###.#.###
|
||||||
|
###v#####.#v#.###.#.###
|
||||||
|
###.>...#.#.#.....#...#
|
||||||
|
###v###.#.#.#########.#
|
||||||
|
###...#.#.#.......#...#
|
||||||
|
#####.#.#.#######.#.###
|
||||||
|
#.....#.#.#.......#...#
|
||||||
|
#.#####.#.#.#########v#
|
||||||
|
#.#...#...#...###...>.#
|
||||||
|
#.#.#v#######v###.###v#
|
||||||
|
#...#.>.#...>.>.#.###.#
|
||||||
|
#####v#.#.###v#.#.###.#
|
||||||
|
#.....#...#...#.#.#...#
|
||||||
|
#.#########.###.#.#.###
|
||||||
|
#...###...#...#...#.###
|
||||||
|
###.###.#.###v#####v###
|
||||||
|
#...#...#.#.>.>.#.>.###
|
||||||
|
#.###.###.#.###.#.#v###
|
||||||
|
#.....###...###...#...#
|
||||||
|
#####################.#
|
||||||
|
"""
|
||||||
|
|
||||||
|
data class Node(val pos: RelPos, val relLen: Int, val path: LinkedHashSet<RelPos>)
|
||||||
|
|
||||||
|
fun part1(input: List<String>): Int {
|
||||||
|
val grid = CharGrid(input, '#')
|
||||||
|
val queue = ArrayDeque<Node>()
|
||||||
|
queue.add(Node(RelPos(1, 0), 0, LinkedHashSet()))
|
||||||
|
var maxLength = 0
|
||||||
|
while (queue.isNotEmpty()) {
|
||||||
|
val node = queue.removeFirst()
|
||||||
|
var pos = node.pos
|
||||||
|
var relLen = node.relLen
|
||||||
|
val path = node.path
|
||||||
|
path.add(pos)
|
||||||
|
var lastPos = pos
|
||||||
|
do {
|
||||||
|
if (pos.dr == grid.height - 1) {
|
||||||
|
maxLength = maxLength.coerceAtLeast(relLen)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
relLen++
|
||||||
|
var nextPos = CharGrid.PLUS_POS
|
||||||
|
.filter { grid[pos.dc + it.dc, pos.dr + it.dr] == '.' && (pos.dc + it.dc != lastPos.dc || pos.dr + it.dr != lastPos.dr) }
|
||||||
|
.map { it.translate(pos) }.singleOrNull { !path.contains(it) }
|
||||||
|
val branchPos = CharGrid.PLUS_POS
|
||||||
|
.filter {
|
||||||
|
val c = grid[pos.dc + it.dc, pos.dr + it.dr]
|
||||||
|
(c == '>' && it.dc > 0) || (c == 'v' && it.dr > 0)
|
||||||
|
|| c == '?'
|
||||||
|
}
|
||||||
|
.map { it.translate(pos) }.filter { !path.contains(it) }
|
||||||
|
|
||||||
|
if (nextPos == null && branchPos.size == 1) {
|
||||||
|
nextPos = branchPos.single()
|
||||||
|
} else {
|
||||||
|
if (branchPos.isNotEmpty()) {
|
||||||
|
path.add(pos)
|
||||||
|
branchPos.forEach { queue.addLast(Node(it, relLen, LinkedHashSet(path))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextPos != null) {
|
||||||
|
lastPos = pos
|
||||||
|
pos = nextPos
|
||||||
|
}
|
||||||
|
} while (nextPos != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GNode(val pos: RelPos, val edges: MutableSet<Pair<Int, GNode>> = HashSet()) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as GNode
|
||||||
|
|
||||||
|
return pos == other.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return pos.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "GNode(pos=$pos)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rec(endnode: GNode, node: GNode, len: Int, visited: Array<BooleanArray>): Int {
|
||||||
|
visited[node.pos.dr][node.pos.dc] = true
|
||||||
|
var maxLen = 0
|
||||||
|
for (e in node.edges) {
|
||||||
|
if (e.second == endnode) {
|
||||||
|
visited[node.pos.dr][node.pos.dc] = false
|
||||||
|
return len + e.first
|
||||||
|
}
|
||||||
|
if (!visited[e.second.pos.dr][e.second.pos.dc]) {
|
||||||
|
val longestPath = rec(endnode, e.second, len + e.first, visited)
|
||||||
|
maxLen = maxLen.coerceAtLeast(longestPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visited[node.pos.dr][node.pos.dc] = false
|
||||||
|
return maxLen
|
||||||
|
}
|
||||||
|
|
||||||
|
fun part2(input: List<String>): Int {
|
||||||
|
val modinput = input.map { it.replace(">", ".").replace("v", ".") }.toList()
|
||||||
|
|
||||||
|
val map = HashMap<RelPos, GNode>()
|
||||||
|
val grid = CharGrid(modinput, '#')
|
||||||
|
val queue = ArrayDeque<Pair<GNode, RelPos>>()
|
||||||
|
val startNode = GNode(RelPos(1, 0))
|
||||||
|
queue.add(startNode to RelPos(0, 1))
|
||||||
|
map[startNode.pos] = startNode
|
||||||
|
|
||||||
|
val endPos = RelPos(grid.width - 2, grid.height - 1)
|
||||||
|
val endNode = GNode(endPos)
|
||||||
|
map[endPos] = endNode
|
||||||
|
val visited = HashSet<GNode>()
|
||||||
|
visited.add(startNode)
|
||||||
|
while (queue.isNotEmpty()) {
|
||||||
|
val (node, dir) = queue.removeFirst()
|
||||||
|
var pos = node.pos.translate(dir)
|
||||||
|
var relLen = 0
|
||||||
|
var lastPos = node.pos
|
||||||
|
do {
|
||||||
|
val nextDirs = CharGrid.PLUS_POS
|
||||||
|
.filter { grid[pos.dc + it.dc, pos.dr + it.dr] == '.' && (pos.dc + it.dc != lastPos.dc || pos.dr + it.dr != lastPos.dr) }
|
||||||
|
|
||||||
|
relLen++
|
||||||
|
if (nextDirs.size == 1) {
|
||||||
|
lastPos = pos
|
||||||
|
pos = pos.translate(nextDirs.single())
|
||||||
|
if (pos == endPos) {
|
||||||
|
node.edges.add(relLen + 1 to endNode)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val branchNode = map.computeIfAbsent(pos) { GNode(it) }
|
||||||
|
branchNode.edges.add(relLen to node)
|
||||||
|
node.edges.add(relLen to branchNode)
|
||||||
|
if (!visited.contains(branchNode)) {
|
||||||
|
visited.add(branchNode)
|
||||||
|
nextDirs.forEach { queue.addLast(branchNode to it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (nextDirs.size == 1)
|
||||||
|
}
|
||||||
|
return rec(endNode, startNode, 0, Array(grid.height) { BooleanArray(grid.width) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if implementation meets criteria from the description, like:
|
||||||
|
val testInput = inlineTestInput.trim().reader().readLines()
|
||||||
|
//val testInput = readInput("aoc2023/Day23_test")
|
||||||
|
val testInputPart1Result = part1(testInput)
|
||||||
|
println("Part 1 Test: $testInputPart1Result")
|
||||||
|
val testInputPart2Result = part2(testInput)
|
||||||
|
println("Part 2 Test: $testInputPart2Result")
|
||||||
|
check(testInputPart1Result == 94)
|
||||||
|
check(testInputPart2Result == 154)
|
||||||
|
|
||||||
|
val input = readInput("aoc2023/Day23")
|
||||||
|
part1(input).println()
|
||||||
|
part2(input).println()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user