generated from Jadarma/advent-of-code-kotlin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Y2021D12.kt
75 lines (66 loc) · 3.22 KB
/
Y2021D12.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package aockt.y2021
import io.github.jadarma.aockt.core.Solution
object Y2021D12 : Solution {
/** A pathfinding service able to navigate between the `start` and `end` caves via the given connections. */
private class Navigator(connections: Iterable<Pair<String, String>>) {
/** Simple data holder representing a cave node, which knows if it is small or large. */
private data class CaveNode(val name: String) {
val isSmall = name.all { it.isLowerCase() }
}
private val cave: Map<CaveNode, Set<CaveNode>> = run {
val connectionsMap = buildMap<String, MutableSet<String>> {
connections.forEach { (a, b) ->
getOrPut(a) { mutableSetOf() }.add(b)
getOrPut(b) { mutableSetOf() }.add(a)
}
}
buildMap {
val nodes = connectionsMap.keys.associateWith { name -> CaveNode(name) }
require(nodes.containsKey("start")) { "Missing start node." }
require(nodes.containsKey("end")) { "Missing end node." }
connectionsMap.forEach { (name, linkedCaves) ->
put(nodes.getValue(name), linkedCaves.map { nodes.getValue(it) }.toSet())
}
}
}
val start = cave.keys.first { it.name == "start" }
val end = cave.keys.first { it.name == "end" }
private suspend fun SequenceScope<List<String>>.continueVia(
node: CaveNode,
pathSoFar: List<CaveNode>,
canMakeException: Boolean,
) {
val nextPath = pathSoFar + node
if (node == end) {
yield(nextPath.map { it.name })
return
}
cave.getValue(node).forEach { nextNode ->
val isEndNode = nextNode == start || nextNode == end
val isInPathAlready = nextNode in pathSoFar
val isAnException = (nextNode.isSmall && isInPathAlready)
when {
nextNode.isSmall && isInPathAlready && !canMakeException -> Unit
isAnException && isEndNode -> Unit
else -> continueVia(nextNode, nextPath, canMakeException && !isAnException)
}
}
}
/**
* Generates all possible paths between the start and end nodes that only pass through small games at most once.
* If [makeExceptionForOneSmallCave] flag is set, only one small cave is allowed to be visited twice.
*/
fun allPaths(makeExceptionForOneSmallCave: Boolean = false): Sequence<List<String>> = sequence {
continueVia(start, emptyList(), makeExceptionForOneSmallCave)
}
}
/** Parse the [input], compute the cave system, and return a [Navigator] instance calibrated to the map. */
private fun parseInput(input: String): Navigator =
input
.lineSequence()
.map { it.split('-').let { (a, b) -> a to b } }
.asIterable()
.let(::Navigator)
override fun partOne(input: String) = parseInput(input).allPaths().count()
override fun partTwo(input: String) = parseInput(input).allPaths(makeExceptionForOneSmallCave = true).count()
}