Skip to content

Commit cf49273

Browse files
committed
add day 16 code
1 parent c3e2952 commit cf49273

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

2022/input/day16

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
Valve XB has flow rate=0; tunnels lead to valves YV, RP
2+
Valve VN has flow rate=0; tunnels lead to valves WL, ET
3+
Valve NT has flow rate=0; tunnels lead to valves CU, MQ
4+
Valve ON has flow rate=0; tunnels lead to valves AA, FP
5+
Valve CW has flow rate=0; tunnels lead to valves UH, WY
6+
Valve KN has flow rate=0; tunnels lead to valves JL, MQ
7+
Valve VT has flow rate=0; tunnels lead to valves CU, UI
8+
Valve CR has flow rate=0; tunnels lead to valves OA, QQ
9+
Valve YX has flow rate=0; tunnels lead to valves YJ, CI
10+
Valve WL has flow rate=7; tunnels lead to valves OQ, VN, PU, VF, UA
11+
Valve HV has flow rate=0; tunnels lead to valves OQ, OK
12+
Valve JM has flow rate=21; tunnels lead to valves RG, OH, JE
13+
Valve XF has flow rate=24; tunnels lead to valves OL, TM
14+
Valve VD has flow rate=0; tunnels lead to valves MY, OK
15+
Valve AA has flow rate=0; tunnels lead to valves KO, ON, UI, QE, VF
16+
Valve JE has flow rate=0; tunnels lead to valves JM, NZ
17+
Valve UN has flow rate=0; tunnels lead to valves UA, WY
18+
Valve CC has flow rate=0; tunnels lead to valves IV, CU
19+
Valve PU has flow rate=0; tunnels lead to valves JL, WL
20+
Valve UA has flow rate=0; tunnels lead to valves WL, UN
21+
Valve OJ has flow rate=13; tunnels lead to valves AZ, FP, MY, OL, ET
22+
Valve CJ has flow rate=0; tunnels lead to valves MQ, WS
23+
Valve IV has flow rate=0; tunnels lead to valves NZ, CC
24+
Valve NZ has flow rate=4; tunnels lead to valves WS, IV, IU, EQ, JE
25+
Valve TM has flow rate=0; tunnels lead to valves HL, XF
26+
Valve SG has flow rate=0; tunnels lead to valves MQ, OH
27+
Valve QQ has flow rate=12; tunnel leads to valve CR
28+
Valve WX has flow rate=15; tunnels lead to valves CI, SN
29+
Valve VF has flow rate=0; tunnels lead to valves WL, AA
30+
Valve RP has flow rate=0; tunnels lead to valves WY, XB
31+
Valve SN has flow rate=0; tunnels lead to valves WX, OI
32+
Valve HL has flow rate=0; tunnels lead to valves OK, TM
33+
Valve ET has flow rate=0; tunnels lead to valves OJ, VN
34+
Valve UI has flow rate=0; tunnels lead to valves AA, VT
35+
Valve FP has flow rate=0; tunnels lead to valves ON, OJ
36+
Valve IU has flow rate=0; tunnels lead to valves NZ, QE
37+
Valve JQ has flow rate=0; tunnels lead to valves HR, CU
38+
Valve CU has flow rate=5; tunnels lead to valves NT, VT, JQ, CC
39+
Valve WY has flow rate=19; tunnels lead to valves CW, UN, RP
40+
Valve YJ has flow rate=16; tunnel leads to valve YX
41+
Valve HR has flow rate=0; tunnels lead to valves JQ, JL
42+
Valve RM has flow rate=11; tunnels lead to valves OI, AZ
43+
Valve RG has flow rate=0; tunnels lead to valves YV, JM
44+
Valve MY has flow rate=0; tunnels lead to valves VD, OJ
45+
Valve QE has flow rate=0; tunnels lead to valves AA, IU
46+
Valve OK has flow rate=17; tunnels lead to valves HL, UH, VD, HV
47+
Valve CI has flow rate=0; tunnels lead to valves WX, YX
48+
Valve OL has flow rate=0; tunnels lead to valves XF, OJ
49+
Valve WS has flow rate=0; tunnels lead to valves CJ, NZ
50+
Valve OH has flow rate=0; tunnels lead to valves JM, SG
51+
Valve OQ has flow rate=0; tunnels lead to valves WL, HV
52+
Valve OA has flow rate=0; tunnels lead to valves CR, MQ
53+
Valve OI has flow rate=0; tunnels lead to valves SN, RM
54+
Valve YV has flow rate=25; tunnels lead to valves RG, XB
55+
Valve JL has flow rate=3; tunnels lead to valves KO, HR, PU, KN, EQ
56+
Valve AZ has flow rate=0; tunnels lead to valves OJ, RM
57+
Valve UH has flow rate=0; tunnels lead to valves CW, OK
58+
Valve KO has flow rate=0; tunnels lead to valves AA, JL
59+
Valve EQ has flow rate=0; tunnels lead to valves NZ, JL
60+
Valve MQ has flow rate=10; tunnels lead to valves CJ, OA, NT, SG, KN

2022/src/day16.scala

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package day16
2+
3+
import locations.Directory.currentDir
4+
import inputs.Input.loadFileSync
5+
6+
@main def part1: Unit =
7+
println(s"The solution is ${part1(loadInput())}")
8+
9+
@main def part2: Unit =
10+
println(s"The solution is ${part2(loadInput())}")
11+
12+
def loadInput(): String = loadFileSync(s"$currentDir/../input/day16")
13+
14+
/*
15+
Copyright 2022 Tyler Coles (javadocmd.com) & Quentin Bernet
16+
17+
Licensed under the Apache License, Version 2.0 (the "License");
18+
you may not use this file except in compliance with the License.
19+
You may obtain a copy of the License at
20+
21+
http://www.apache.org/licenses/LICENSE-2.0
22+
23+
Unless required by applicable law or agreed to in writing, software
24+
distributed under the License is distributed on an "AS IS" BASIS,
25+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26+
See the License for the specific language governing permissions and
27+
limitations under the License.
28+
*/
29+
30+
type Id = String
31+
case class Room(id: Id, flow: Int, tunnels: List[Id])
32+
33+
type Input = List[Room]
34+
// $_ to avoid tunnel/tunnels distinction and so on
35+
def parse(xs: String): Input = xs.split("\n").map{ case s"Valve $id has flow rate=$flow; tunnel$_ lead$_ to valve$_ $tunnelsStr" =>
36+
val tunnels = tunnelsStr.split(", ").toList
37+
Room(id, flow.toInt, tunnels)
38+
}.toList
39+
40+
case class RoomsInfo(
41+
/** map of rooms by id */
42+
rooms: Map[Id, Room],
43+
/** map from starting room to a map containing the best distance to all other rooms */
44+
routes: Map[Id, Map[Id, Int]],
45+
/** rooms containing non-zero-flow valves */
46+
valves: Set[Id]
47+
)
48+
49+
// precalculate useful things like pathfinding
50+
def constructInfo(input: Input): RoomsInfo =
51+
val rooms: Map[Id, Room] = Map.from(for r <- input yield r.id -> r)
52+
val valves: Set[Id] = Set.from(for r <- input if r.flow > 0 yield r.id)
53+
val tunnels: Map[Id, List[Id]] = rooms.mapValues(_.tunnels).toMap
54+
val routes: Map[Id, Map[Id, Int]] = (valves + "AA").iterator.map{ id => id -> computeRoutes(id, tunnels) }.toMap
55+
RoomsInfo(rooms, routes, valves)
56+
57+
// a modified A-star to calculate the best distance to all rooms rather then the best path to a single room
58+
def computeRoutes(start: Id, neighbors: Id => List[Id]): Map[Id, Int] =
59+
60+
case class State(frontier: List[(Id, Int)], scores: Map[Id, Int]):
61+
62+
private def getScore(id: Id): Int = scores.getOrElse(id, Int.MaxValue)
63+
private def setScore(id: Id, s: Int) = State((id, s + 1) :: frontier, scores + (id -> s))
64+
65+
def dequeued: (Id, State) =
66+
val sorted = frontier.sortBy(_._2)
67+
(sorted.head._1, copy(frontier = sorted.tail))
68+
69+
def considerEdge(from: Id, to: Id): State =
70+
val toScore = getScore(from) + 1
71+
if toScore >= getScore(to) then this
72+
else setScore(to, toScore)
73+
end State
74+
75+
object State:
76+
def initial(start: Id) = State(List((start, 0)), Map(start -> 0))
77+
78+
def recurse(state: State): State =
79+
if state.frontier.isEmpty then
80+
state
81+
else
82+
val (curr, currState) = state.dequeued
83+
val newState = neighbors(curr)
84+
.foldLeft(currState) { (s, n) =>
85+
s.considerEdge(curr, n)
86+
}
87+
recurse(newState)
88+
89+
recurse(State.initial(start)).scores
90+
91+
end computeRoutes
92+
93+
94+
// find the best path (the order of valves to open) and the total pressure released by taking it
95+
def bestPath(map: RoomsInfo, start: Id, valves: Set[Id], timeAllowed: Int): Int =
96+
// each step involves moving to a room with a useful valve and opening it
97+
// we don't need to track each (empty) room in between
98+
// we limit our options by only considering the still-closed valves
99+
// and `valves` has already culled any room with a flow value of 0 -- no point in considering these rooms!
100+
101+
def recurse(path: List[Id], valvesLeft: Set[Id], timeLeft: Int, totalValue: Int): Int =
102+
// recursively consider all plausible options
103+
// we are finished when we no longer have time to reach another valve or all valves are open
104+
valvesLeft
105+
.flatMap{ id =>
106+
val current = path.head
107+
val distance = map.routes(current)(id)
108+
// how much time is left after we traverse there and open the valve?
109+
val t = timeLeft - distance - 1
110+
// if `t` is zero or less this option can be skipped
111+
Option.when(t > 0) {
112+
// the value of choosing a particular valve (over the life of our simulation)
113+
// is its flow rate multiplied by the time remaining after opening it
114+
val value = map.rooms(id).flow * t
115+
recurse(id :: path, valvesLeft - id, t, totalValue + value)
116+
}
117+
}
118+
.maxOption
119+
.getOrElse { totalValue }
120+
end recurse
121+
recurse(start :: Nil, valves, timeAllowed, 0)
122+
123+
def part1(input: String) =
124+
val time = 30
125+
val map = constructInfo(parse(input))
126+
bestPath(map, "AA", map.valves, time)
127+
end part1
128+
129+
def part2(input: String) =
130+
val time = 26
131+
val map = constructInfo(parse(input))
132+
133+
// in the optimal solution, the elephant and I will have divided responsibility for switching the valves
134+
// 15 (useful valves) choose 7 (half) yields only 6435 possible divisions which is a reasonable search space!
135+
val valvesA = map.valves.toList
136+
.combinations(map.valves.size / 2)
137+
.map(_.toSet)
138+
139+
// NOTE: I assumed an even ditribution of valves would be optimal, and that turned out to be true.
140+
// However I suppose it's possible an uneven distribution could have been optimal for some graphs.
141+
// To be safe, you could re-run this using all reasonable values of `n` for `combinations` (1 to 7) and
142+
// taking the best of those.
143+
144+
// we can now calculate the efforts separately and sum their values to find the best
145+
val allPaths =
146+
for va <- valvesA yield
147+
val vb = map.valves -- va
148+
val scoreA = bestPath(map, "AA", va, time)
149+
val scoreB = bestPath(map, "AA", vb, time)
150+
scoreA + scoreB
151+
152+
allPaths.max
153+
end part2

0 commit comments

Comments
 (0)