diff --git a/advent-of-code/src/main/kotlin/com/akikanellis/adventofcode/year2022/Day16.kt b/advent-of-code/src/main/kotlin/com/akikanellis/adventofcode/year2022/Day16.kt new file mode 100644 index 0000000..f64c938 --- /dev/null +++ b/advent-of-code/src/main/kotlin/com/akikanellis/adventofcode/year2022/Day16.kt @@ -0,0 +1,153 @@ +package com.akikanellis.adventofcode.year2022 + +object Day16 { + private val VALVES_REGEX = + "Valve (.+) has flow rate=(.+); tunnels? leads? to valves? (.+)".toRegex() + + fun highestPressureThanCanBeReleased(input: String, elephantWillHelp: Boolean) = + valves(input, elephantWillHelp) + .highestPressureThanCanBeReleased() + + private fun valves(input: String, elephantWillHelp: Boolean): Valves { + val valvesToNeighbourNames = valvesToNeighbourNames(input) + val valves = valvesToNeighbourNames.map { (valve, _) -> valve } + + valvesToNeighbourNames.forEach { (valve, neighbourNames) -> + valve.neighbours = valves.filter { it.name in neighbourNames } + } + valves.forEach { valve -> initialiseDistancesToOtherValves(valve) } + + return Valves( + valves = valves, + elephantWillHelp = elephantWillHelp + ) + } + + private fun valvesToNeighbourNames(input: String) = input.lines() + .filter { it.isNotBlank() } + .map { line -> + val groupValues = VALVES_REGEX.matchEntire(line)!!.groupValues + Pair( + Valve( + name = groupValues[1], + flowRate = groupValues[2].toInt() + ), + groupValues[3].split(", ") + ) + } + + private fun initialiseDistancesToOtherValves(valve: Valve) { + val valveNameToShortestDistance = mutableMapOf() + .apply { put(valve.name, 0) } + + val remainingValves = mutableListOf(valve) + while (remainingValves.isNotEmpty()) { + val currentValve = remainingValves.removeFirst() + val updatedDistanceToCurrentValveNeighbours = + valveNameToShortestDistance[currentValve.name]!! + 1 + + currentValve.neighbours.forEach { currentValveNeighbour -> + val precomputedDistanceToCurrentValveNeighbour = + valveNameToShortestDistance.getOrDefault( + currentValveNeighbour.name, + Int.MAX_VALUE + ) + + if ( + updatedDistanceToCurrentValveNeighbours < + precomputedDistanceToCurrentValveNeighbour + ) { + valveNameToShortestDistance[currentValveNeighbour.name] = + updatedDistanceToCurrentValveNeighbours + remainingValves += currentValveNeighbour + } + } + } + + valve.valveNameToShortestDistance = valveNameToShortestDistance + } + + private class Valves( + val startValve: Valve, + val openableValves: Set, + val elephantWillHelp: Boolean, + val elephantIsHelping: Boolean + ) { + constructor( + valves: List, + elephantWillHelp: Boolean + ) : this( + startValve = valves.single { it.name == "AA" }, + openableValves = valves.filter { it.flowRate > 0 }.toSet(), + elephantWillHelp = elephantWillHelp, + elephantIsHelping = false + ) + + val totalMinutes = if (elephantWillHelp || elephantIsHelping) 26 else 30 + val precomputedSystemStateToMaxPressure = mutableMapOf() + + fun highestPressureThanCanBeReleased() = highestPressureThanCanBeReleased( + remainingMinutes = totalMinutes, + currentValve = startValve, + remainingValves = openableValves + ) + + private fun highestPressureThanCanBeReleased( + remainingMinutes: Int, + currentValve: Valve, + remainingValves: Set + ): Int { + val currentValvePressure = remainingMinutes * currentValve.flowRate + val currentSystemState = SystemState( + currentValveName = currentValve.name, + remainingMinutes = remainingMinutes, + remainingValves = remainingValves + ) + + return currentValvePressure + precomputedSystemStateToMaxPressure + .getOrPut(currentSystemState) { + val remainingValvesMaxPressure = remainingValves + .filter { nextValve -> + currentValve.distanceTo(nextValve) < remainingMinutes + } + .maxOfOrNull { nextValve -> + val remainingMinutesForNextValve = + remainingMinutes - currentValve.distanceTo(nextValve) - 1 + + highestPressureThanCanBeReleased( + remainingMinutes = remainingMinutesForNextValve, + currentValve = nextValve, + remainingValves = remainingValves - nextValve + ) + } ?: 0 + + if (elephantWillHelp && !elephantIsHelping) { + maxOf( + remainingValvesMaxPressure, + Valves( + startValve = startValve, + openableValves = remainingValves, + elephantWillHelp = false, + elephantIsHelping = true + ).highestPressureThanCanBeReleased() + ) + } else { + remainingValvesMaxPressure + } + } + } + } + + private data class Valve(val name: String, val flowRate: Int) { + lateinit var neighbours: List + lateinit var valveNameToShortestDistance: Map + + fun distanceTo(other: Valve) = valveNameToShortestDistance[other.name]!! + } + + private data class SystemState( + val currentValveName: String, + val remainingMinutes: Int, + val remainingValves: Set + ) +} diff --git a/advent-of-code/src/test/kotlin/com/akikanellis/adventofcode/year2022/Day16Test.kt b/advent-of-code/src/test/kotlin/com/akikanellis/adventofcode/year2022/Day16Test.kt new file mode 100644 index 0000000..de7e67d --- /dev/null +++ b/advent-of-code/src/test/kotlin/com/akikanellis/adventofcode/year2022/Day16Test.kt @@ -0,0 +1,28 @@ +package com.akikanellis.adventofcode.year2022 + +import com.akikanellis.adventofcode.testutils.resourceText +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.assertEquals + +class Day16Test { + @ParameterizedTest + @CsvSource( + "/day-16-input-example.txt, false, 1_651", + "/day-16-input-puzzle.txt, false, 1_653", + "/day-16-input-example.txt, true, 1_707", + "/day-16-input-puzzle.txt, true, 2_223" + ) + fun `finds highest pressure that can be released`( + inputFile: String, + elephantWillHelp: Boolean, + expectedPressure: Int + ) { + val input = resourceText(inputFile) + + val highestPressureThanCanBeReleased = + Day16.highestPressureThanCanBeReleased(input, elephantWillHelp) + + assertEquals(expectedPressure, highestPressureThanCanBeReleased) + } +} diff --git a/advent-of-code/src/test/resources/day-16-input-example.txt b/advent-of-code/src/test/resources/day-16-input-example.txt new file mode 100644 index 0000000..9f30acc --- /dev/null +++ b/advent-of-code/src/test/resources/day-16-input-example.txt @@ -0,0 +1,10 @@ +Valve AA has flow rate=0; tunnels lead to valves DD, II, BB +Valve BB has flow rate=13; tunnels lead to valves CC, AA +Valve CC has flow rate=2; tunnels lead to valves DD, BB +Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE +Valve EE has flow rate=3; tunnels lead to valves FF, DD +Valve FF has flow rate=0; tunnels lead to valves EE, GG +Valve GG has flow rate=0; tunnels lead to valves FF, HH +Valve HH has flow rate=22; tunnel leads to valve GG +Valve II has flow rate=0; tunnels lead to valves AA, JJ +Valve JJ has flow rate=21; tunnel leads to valve II diff --git a/advent-of-code/src/test/resources/day-16-input-puzzle.txt b/advent-of-code/src/test/resources/day-16-input-puzzle.txt new file mode 100644 index 0000000..2727099 --- /dev/null +++ b/advent-of-code/src/test/resources/day-16-input-puzzle.txt @@ -0,0 +1,64 @@ +Valve MU has flow rate=0; tunnels lead to valves VT, LA +Valve TQ has flow rate=0; tunnels lead to valves HU, SU +Valve YH has flow rate=0; tunnels lead to valves CN, BN +Valve EO has flow rate=0; tunnels lead to valves IK, CN +Valve MH has flow rate=0; tunnels lead to valves GG, HG +Valve RJ has flow rate=0; tunnels lead to valves AA, RI +Valve XZ has flow rate=0; tunnels lead to valves PX, VT +Valve UU has flow rate=0; tunnels lead to valves DT, XG +Valve KV has flow rate=13; tunnels lead to valves HN, CV, PE, XD, TA +Valve SU has flow rate=19; tunnels lead to valves TQ, HF, OL, SF +Valve BB has flow rate=0; tunnels lead to valves NS, HR +Valve RI has flow rate=4; tunnels lead to valves ML, EE, TZ, RJ, PE +Valve TZ has flow rate=0; tunnels lead to valves VT, RI +Valve LY has flow rate=0; tunnels lead to valves EE, RP +Valve PX has flow rate=0; tunnels lead to valves XZ, JQ +Valve VH has flow rate=0; tunnels lead to valves DT, TA +Valve HN has flow rate=0; tunnels lead to valves KV, LR +Valve LR has flow rate=0; tunnels lead to valves HR, HN +Valve NJ has flow rate=0; tunnels lead to valves QF, JC +Valve AM has flow rate=0; tunnels lead to valves OJ, AA +Valve FM has flow rate=0; tunnels lead to valves VT, RP +Valve VT has flow rate=5; tunnels lead to valves IP, XZ, TZ, FM, MU +Valve HF has flow rate=0; tunnels lead to valves NR, SU +Valve HR has flow rate=11; tunnels lead to valves BB, KO, LR +Valve WX has flow rate=0; tunnels lead to valves CN, IP +Valve PE has flow rate=0; tunnels lead to valves KV, RI +Valve QF has flow rate=17; tunnels lead to valves YI, NJ +Valve EE has flow rate=0; tunnels lead to valves LY, RI +Valve UH has flow rate=25; tunnel leads to valve YI +Valve CV has flow rate=0; tunnels lead to valves KV, NS +Valve SF has flow rate=0; tunnels lead to valves YN, SU +Valve RP has flow rate=3; tunnels lead to valves HG, FM, OJ, IK, LY +Valve XD has flow rate=0; tunnels lead to valves IL, KV +Valve GG has flow rate=12; tunnels lead to valves ML, IL, MH, OL, KA +Valve XG has flow rate=0; tunnels lead to valves LI, UU +Valve YA has flow rate=21; tunnels lead to valves UJ, GQ +Valve OL has flow rate=0; tunnels lead to valves GG, SU +Valve AN has flow rate=0; tunnels lead to valves AA, IX +Valve LI has flow rate=15; tunnel leads to valve XG +Valve GQ has flow rate=0; tunnels lead to valves YA, KO +Valve HU has flow rate=0; tunnels lead to valves TQ, DT +Valve OJ has flow rate=0; tunnels lead to valves RP, AM +Valve YN has flow rate=0; tunnels lead to valves SF, JQ +Valve ML has flow rate=0; tunnels lead to valves RI, GG +Valve UJ has flow rate=0; tunnels lead to valves YA, NS +Valve IX has flow rate=0; tunnels lead to valves AN, JQ +Valve JC has flow rate=0; tunnels lead to valves JQ, NJ +Valve TA has flow rate=0; tunnels lead to valves KV, VH +Valve DT has flow rate=16; tunnels lead to valves UU, HU, KA, VH +Valve NR has flow rate=0; tunnels lead to valves HF, CN +Valve YI has flow rate=0; tunnels lead to valves QF, UH +Valve AA has flow rate=0; tunnels lead to valves AM, AN, BN, LA, RJ +Valve BN has flow rate=0; tunnels lead to valves AA, YH +Valve KA has flow rate=0; tunnels lead to valves GG, DT +Valve IL has flow rate=0; tunnels lead to valves GG, XD +Valve CN has flow rate=7; tunnels lead to valves YH, EO, WX, NR, OM +Valve IP has flow rate=0; tunnels lead to valves WX, VT +Valve OM has flow rate=0; tunnels lead to valves CN, JQ +Valve KO has flow rate=0; tunnels lead to valves GQ, HR +Valve LA has flow rate=0; tunnels lead to valves AA, MU +Valve JQ has flow rate=6; tunnels lead to valves IX, JC, PX, YN, OM +Valve IK has flow rate=0; tunnels lead to valves EO, RP +Valve HG has flow rate=0; tunnels lead to valves MH, RP +Valve NS has flow rate=23; tunnels lead to valves CV, BB, UJ