-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package me.gergo | ||
|
||
import java.io.File | ||
import kotlin.math.max | ||
|
||
fun main() { | ||
val valves = File("src/main/resources/input16.txt").readLines() | ||
.mapIndexed(::parseValves) | ||
val valvesByName = valves.associateBy(Valve::name) | ||
val neighbors = valves.associateWith { v -> v.tunnelsTo.map { valvesByName[it]!! } } | ||
|
||
// Part One - dynamic programming, memoization | ||
data class MemoKey(val valve: Valve, val opened: Set<Valve>, val minute: Int) | ||
|
||
val memo = mutableMapOf<MemoKey, Int>() | ||
|
||
fun findMaxPressure(current: Valve, opened: Set<Valve>, minute: Int): Int { | ||
val key = MemoKey(current, opened, minute) | ||
var max = memo[key] | ||
if (max == null) { | ||
val pressureReleased = opened.sumOf(Valve::rate) | ||
max = pressureReleased + if (minute == 30) 0 | ||
else { | ||
max( | ||
if (current.rate > 0 && !opened.contains(current)) // Stay here and open current valve (if the valve is not damaged) | ||
findMaxPressure(current, opened.plus(current), minute + 1) else 0, | ||
neighbors[current]!!.maxOf { // ...or don't open the valve and move to a neighbor | ||
findMaxPressure(it, opened, minute + 1) | ||
} | ||
) | ||
} | ||
memo[key] = max | ||
} | ||
return max | ||
} | ||
|
||
// FUK. I spent HOURS wondering why the sample data passes and my puzzle input does not... "You start at valve AA", NOT the first valve :| | ||
val aa = valves.first { it.name == "AA" } | ||
|
||
val result1 = findMaxPressure(aa, emptySet(), 1) | ||
println("Max pressure released : $result1") | ||
|
||
// Part Two | ||
data class MemoKey2( | ||
val me: Valve, | ||
val eli: Valve, | ||
val opened: Long, | ||
val openedRate: Int, | ||
val meVisited: Long, | ||
val eliVisited: Long, | ||
val minute: Int | ||
) { | ||
fun meCanOpen() = me.rate > 0 && !opened.isBitSet(me.index) | ||
fun eliCanOpen() = eli.rate > 0 && !opened.isBitSet(eli.index) | ||
|
||
fun meNeighbors() = neighbors[me]!! | ||
fun eliNeighbors() = neighbors[eli]!! | ||
|
||
fun meMovesEliOpens(neighbor: Valve) = | ||
MemoKey2(neighbor, eli, opened.setBit(eli.index), openedRate + eli.rate, meVisited.setBit(me.index), eliVisited, minute + 1) | ||
|
||
fun eliMovesMeOpens(neighbor: Valve) = | ||
MemoKey2(me, neighbor, opened.setBit(me.index), openedRate + me.rate, meVisited, eliVisited.setBit(eli.index), minute + 1) | ||
|
||
fun weBothOpen() = | ||
MemoKey2(me, eli, opened.setBit(me.index).setBit(eli.index), openedRate + me.rate + eli.rate, meVisited, eliVisited, minute + 1) | ||
|
||
fun weBothMove(n1: Valve, n2: Valve) = | ||
MemoKey2(n1, n2, opened, openedRate, meVisited.setBit(me.index), eliVisited.setBit(eli.index), minute + 1) | ||
} | ||
|
||
val memo2 = mutableMapOf<MemoKey2, Int>() | ||
|
||
fun permutations(k: MemoKey2) = sequence { | ||
if (k.meCanOpen() && k.eliCanOpen() && k.me != k.eli) yield(k.weBothOpen()) // Both valves can be opened | ||
if (k.meCanOpen()) | ||
for (n in k.eliNeighbors()) { // My valve can be opened, Eli can move | ||
if (k.meVisited.isBitSet(n.index)) continue // Eli shouldn't go where I went | ||
yield(k.eliMovesMeOpens(n)) | ||
} | ||
if (k.eliCanOpen()) | ||
for (n in k.meNeighbors()) { // Eli's valve can be opened, I can move | ||
if (k.eliVisited.isBitSet(n.index)) continue // I shouldn't go where Eli went | ||
yield(k.meMovesEliOpens(n)) | ||
} | ||
for (mn in k.meNeighbors()) { // We both move | ||
if (k.eliVisited.isBitSet(mn.index)) continue // I shouldn't go where Eli went | ||
for (en in k.eliNeighbors()) { | ||
if (k.meVisited.isBitSet(en.index)) continue // Eli shouldn't go where I went | ||
yield(k.weBothMove(mn, en)) | ||
} | ||
} | ||
} | ||
|
||
fun findMaxPressureWithElephant(key: MemoKey2): Int { | ||
if (key.minute == 26) return key.openedRate // We're done! | ||
|
||
val cachedMax = memo2[key] | ||
if (cachedMax != null) return cachedMax | ||
|
||
val max = key.openedRate + (permutations(key).map(::findMaxPressureWithElephant).maxOrNull() ?: 0) | ||
memo2[key] = max | ||
|
||
if (memo2.size % 1000000 == 0) println("Memo entries: ${memo2.size}") | ||
return max | ||
} | ||
|
||
val result2 = findMaxPressureWithElephant(MemoKey2(aa, aa, 0L, 0, 0L, 0L, 1)) | ||
println("Max pressure released with Eli: $result2") | ||
} | ||
|
||
private fun Long.isBitSet(index: Int): Boolean = this and (1L shl index) != 0L | ||
private fun Long.setBit(index: Int): Long = this or (1L shl index) | ||
|
||
private data class Valve(val index: Int, val name: String, val rate: Int, val tunnelsTo: List<String>) | ||
|
||
private val ValveTunnelFormat = Regex("Valve (\\w+) has flow rate=(\\d+); tunnels? leads? to valves? (.*)") | ||
private fun parseValves(i: Int, line: String): Valve { | ||
val (_, name, rate, tunnels) = ValveTunnelFormat.matchEntire(line)!!.groupValues | ||
return Valve(i, name, rate.toInt(), tunnels.split(", ")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
Valve OQ has flow rate=17; tunnels lead to valves NB, AK, KL | ||
Valve HP has flow rate=0; tunnels lead to valves ZX, KQ | ||
Valve GO has flow rate=0; tunnels lead to valves HR, GW | ||
Valve PD has flow rate=9; tunnels lead to valves XN, EV, QE, MW | ||
Valve NQ has flow rate=0; tunnels lead to valves HX, ZX | ||
Valve DW has flow rate=0; tunnels lead to valves IR, WE | ||
Valve TN has flow rate=24; tunnels lead to valves KL, EI | ||
Valve JJ has flow rate=0; tunnels lead to valves EV, HR | ||
Valve KH has flow rate=0; tunnels lead to valves ZQ, AA | ||
Valve PH has flow rate=0; tunnels lead to valves FN, QE | ||
Valve FD has flow rate=0; tunnels lead to valves SM, HX | ||
Valve SM has flow rate=7; tunnels lead to valves WW, RZ, FD, HO, KQ | ||
Valve PU has flow rate=0; tunnels lead to valves VL, IR | ||
Valve OM has flow rate=0; tunnels lead to valves CM, AA | ||
Valve KX has flow rate=20; tunnel leads to valve PC | ||
Valve IR has flow rate=3; tunnels lead to valves PU, CM, WW, DW, AF | ||
Valve XG has flow rate=0; tunnels lead to valves RX, OF | ||
Valve QE has flow rate=0; tunnels lead to valves PH, PD | ||
Valve GW has flow rate=0; tunnels lead to valves JQ, GO | ||
Valve HO has flow rate=0; tunnels lead to valves SM, TY | ||
Valve WU has flow rate=0; tunnels lead to valves SG, RZ | ||
Valve MS has flow rate=0; tunnels lead to valves UE, OF | ||
Valve JS has flow rate=0; tunnels lead to valves DO, ZX | ||
Valve YQ has flow rate=0; tunnels lead to valves BC, SG | ||
Valve EJ has flow rate=0; tunnels lead to valves AA, LR | ||
Valve EI has flow rate=0; tunnels lead to valves BV, TN | ||
Valve NC has flow rate=0; tunnels lead to valves TS, BC | ||
Valve AF has flow rate=0; tunnels lead to valves IR, HX | ||
Valve OX has flow rate=0; tunnels lead to valves HR, BV | ||
Valve BF has flow rate=0; tunnels lead to valves JQ, SY | ||
Valve CA has flow rate=0; tunnels lead to valves YD, HX | ||
Valve KQ has flow rate=0; tunnels lead to valves HP, SM | ||
Valve NB has flow rate=0; tunnels lead to valves OQ, OF | ||
Valve SY has flow rate=0; tunnels lead to valves BF, BV | ||
Valve AA has flow rate=0; tunnels lead to valves KH, EJ, OM, TY, DO | ||
Valve BC has flow rate=11; tunnels lead to valves WE, RX, YQ, LR, NC | ||
Valve HR has flow rate=14; tunnels lead to valves OX, GO, JJ | ||
Valve WE has flow rate=0; tunnels lead to valves DW, BC | ||
Valve MW has flow rate=0; tunnels lead to valves JQ, PD | ||
Valve DO has flow rate=0; tunnels lead to valves JS, AA | ||
Valve PC has flow rate=0; tunnels lead to valves AK, KX | ||
Valve YD has flow rate=0; tunnels lead to valves CA, OF | ||
Valve RX has flow rate=0; tunnels lead to valves XG, BC | ||
Valve CM has flow rate=0; tunnels lead to valves IR, OM | ||
Valve HX has flow rate=6; tunnels lead to valves ZQ, NQ, AF, FD, CA | ||
Valve ZQ has flow rate=0; tunnels lead to valves KH, HX | ||
Valve BV has flow rate=21; tunnels lead to valves SY, OX, EI | ||
Valve AK has flow rate=0; tunnels lead to valves PC, OQ | ||
Valve UE has flow rate=0; tunnels lead to valves MS, JQ | ||
Valve LR has flow rate=0; tunnels lead to valves BC, EJ | ||
Valve JQ has flow rate=8; tunnels lead to valves MW, UE, BF, GW | ||
Valve VL has flow rate=0; tunnels lead to valves PU, ZX | ||
Valve EV has flow rate=0; tunnels lead to valves JJ, PD | ||
Valve TS has flow rate=0; tunnels lead to valves NC, ZX | ||
Valve RZ has flow rate=0; tunnels lead to valves SM, WU | ||
Valve OF has flow rate=13; tunnels lead to valves XG, YD, NB, MS, XN | ||
Valve WW has flow rate=0; tunnels lead to valves SM, IR | ||
Valve TY has flow rate=0; tunnels lead to valves HO, AA | ||
Valve XN has flow rate=0; tunnels lead to valves OF, PD | ||
Valve SG has flow rate=15; tunnels lead to valves WU, YQ | ||
Valve FN has flow rate=25; tunnel leads to valve PH | ||
Valve KL has flow rate=0; tunnels lead to valves TN, OQ | ||
Valve ZX has flow rate=5; tunnels lead to valves JS, HP, VL, NQ, TS |