-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay16.kt
122 lines (101 loc) · 4.41 KB
/
Day16.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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package tr.emreone.adventofcode.days
import java.util.*
import kotlin.collections.HashMap
object Day16 {
// Regex to match comma seperated words
val VALVE_PATTERN = """^Valve (?<name>\w+) has flow rate=(?<flowRate>\d+); tunnel(s)? lead(s)? to valve(s)? (?<adj>[\w\s,]+)$""".toRegex()
data class State(
var at: String,
var remainingTime: Int,
var elephantAt: String? = null,
var remainingElTime: Int? = null,
var openedValves: Set<String> = setOf(),
var totalFlow: Int = 0,
) : Comparable<State> {
override fun compareTo(other: State) = compareValuesBy(this, other) { -it.totalFlow }
}
data class Valve(val name: String, val flowRate: Int, val adjTunnels: List<String>)
class TunnelSystem {
lateinit var valves: Map<String, Valve>
lateinit var nonZeroNeighborsMap: Map<String, Map<String, Int>>
fun initTunnelsystem() {
this.nonZeroNeighborsMap = this.valves.keys.associateWith {
getNonZeroNeighbors(it)
}
}
fun getNonZeroNeighbors(curr: String, dist: Int = 0, visited: Set<String> = setOf()): Map<String, Int> {
val neighbours = HashMap<String, Int>()
for (adj in valves[curr]!!.adjTunnels.filter { it !in visited }) {
if (valves[adj]!!.flowRate != 0) {
neighbours[adj] = dist+1
}
for ((name, d) in getNonZeroNeighbors(adj, dist+1, visited + setOf(curr))) {
neighbours[name] = minOf(d, neighbours.getOrDefault(name, 1000))
}
}
return neighbours
}
fun solve(initialState: State): Int {
val queue = PriorityQueue<State>().also { it.add(initialState) }
var best = 0
val visited: MutableMap<List<String>, Int> = mutableMapOf()
while (queue.isNotEmpty()) {
var (at, remainingTime,
elephantAt, remainingElTime,
openedValves, totalFlow) = queue.remove()
best = maxOf(best, totalFlow)
val vis = (openedValves.toList() + listOf(at, elephantAt ?: "")).sorted()
if (visited.getOrDefault(vis, -1) >= totalFlow)
continue
visited[vis] = totalFlow
if (remainingElTime != null && elephantAt != null && remainingTime < remainingElTime) {
remainingTime = remainingElTime.also {
remainingElTime = remainingTime
}
at = elephantAt.also {
elephantAt = at
}
}
for ((neighbor, dist) in nonZeroNeighborsMap[at]!!) {
val newTime = remainingTime - dist - 1
val newFlow = totalFlow + this.valves[neighbor]!!.flowRate * newTime
if (newTime >= 0 && neighbor !in openedValves)
queue.add(
State(neighbor, newTime,
elephantAt, remainingElTime,
openedValves + setOf(neighbor), newFlow)
)
}
}
return best
}
companion object {
fun parseFrom(input: List<String>): TunnelSystem {
return TunnelSystem().apply {
valves = input
.map {line ->
val matches = VALVE_PATTERN.matchEntire(line)!!.groups as MatchNamedGroupCollection
matches.let {
Valve(
it["name"]!!.value,
it["flowRate"]!!.value.toInt(),
it["adj"]!!.value.split(", ")
)
}
}
.associateBy { it.name }
}
}
}
}
fun part1(input: List<String>): Int {
val tunnelSystem = TunnelSystem.parseFrom(input)
tunnelSystem.initTunnelsystem()
return tunnelSystem.solve(State("AA", 30))
}
fun part2(input: List<String>): Int {
val tunnelSystem = TunnelSystem.parseFrom(input)
tunnelSystem.initTunnelsystem()
return tunnelSystem.solve(State("AA", 26, "AA", 26))
}
}