github.com/mnlphlp/aoc22@v0.0.0-20230330151331-c1dc4bff1b9b/day16/day16.go (about) 1 package day16 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 ) 9 10 type valve struct { 11 name string 12 flow int 13 f int 14 timeToOpen int 15 open bool 16 connections []string 17 id int64 18 inPath int64 19 curFlow int 20 hash complex128 21 openedCount int 22 } 23 24 func (v *valve) calc(time int, current valve) { 25 v.inPath = current.inPath 26 v.timeToOpen = current.timeToOpen + 1 27 v.f = current.f + v.flow*(time-v.timeToOpen) 28 v.curFlow = current.curFlow + v.flow 29 v.openedCount = current.openedCount 30 if v.open { 31 v.inPath = v.inPath | v.id 32 v.openedCount = current.openedCount + 1 33 } 34 open := int64(0) 35 if v.open { 36 open = 1 37 } 38 v.hash = complex(float64(v.inPath), float64(v.id+open)) 39 } 40 41 func (v valve) String() string { 42 return fmt.Sprintf("Valve: %s (%d), Flow: %d, Connections to: %v\n Path: %d\n curFlow: %v", 43 v.name, v.id, v.flow, v.connections, v.inPath, v.curFlow) 44 } 45 46 func parseValves(input string) map[string]valve { 47 valves := make(map[string]valve) 48 connections := make(map[string]string) 49 for i, l := range strings.Split(string(input), "\n") { 50 if l == "" { 51 continue 52 } 53 parts := strings.Split(l, " ") 54 connection := "" 55 for _, p := range parts[9:] { 56 connection += p 57 } 58 flow, _ := strconv.Atoi(parts[4][5 : len(parts[4])-1]) 59 id := int64(1) << i 60 valves[parts[1]+"C"] = valve{ 61 name: parts[1] + "C", 62 flow: 0, 63 id: id, 64 } 65 valves[parts[1]+"O"] = valve{ 66 name: parts[1] + "O", 67 flow: flow, 68 open: true, 69 id: id, 70 } 71 connections[parts[1]+"O"] = connection 72 connections[parts[1]+"C"] = connection 73 } 74 for name, connection := range connections { 75 val := valves[name] 76 for _, c := range strings.Split(connection, ",") { 77 val.connections = append(val.connections, c+"C") 78 } 79 val.connections = append(val.connections, name[:len(name)-1]+"C") 80 if !valves[name].open { 81 val.connections = append(val.connections, name[:len(name)-1]+"O") 82 } 83 valves[name] = val 84 } 85 return valves 86 } 87 88 func findMaxPressure(valves map[string]valve, time int, startPoint string, multipleWorkers bool) (int, map[int64]valve) { 89 open := make([]valve, 0) 90 openHash := make(map[complex128]bool) 91 // start with closed version of first valve 92 current := valves[startPoint+"C"] 93 current.calc(time, valve{ 94 timeToOpen: -1, 95 }) 96 open = append(open, current) 97 bestGoal := current 98 bestPaths := make(map[int64]valve) 99 cache := make(map[int64]int) 100 maxFlow := 0 101 workingValves := 0 102 for _, v := range valves { 103 maxFlow += v.flow 104 if v.flow > 0 { 105 workingValves++ 106 } 107 } 108 // explore paths using a* like algorithm 109 for len(open) > 0 { 110 // choose best item of open list 111 maxScore := 0 112 for i, v := range open { 113 if v.f > open[maxScore].f { 114 maxScore = i 115 } 116 } 117 current = open[maxScore] 118 open = append(open[:maxScore], open[maxScore+1:]...) 119 delete(openHash, current.hash) 120 121 // check if better solution is found 122 if current.timeToOpen == time-1 { 123 overallPaths++ 124 if current.f > bestGoal.f { 125 bestGoal = current 126 } 127 // check paths 128 bestPath, ok := bestPaths[current.inPath] 129 balanced := true 130 if multipleWorkers { 131 percentOpen := float32(current.openedCount) / float32(workingValves) 132 if percentOpen < 0.3 || percentOpen > 0.7 { 133 balanced = false 134 } 135 } 136 if (!ok || current.f > bestPath.f) && balanced { 137 bestPaths[current.inPath] = current 138 } 139 //} 140 } 141 142 for _, v := range current.connections { 143 // check if trying to open already open valve or valve without flow 144 if valves[v].open && (current.inPath&valves[v].id > 0 || valves[v].flow == 0) { 145 continue 146 } 147 val := valves[v] 148 val.calc(time, current) 149 pressure, ok := cache[val.inPath] 150 if ok && pressure > val.f { 151 cacheHit++ 152 continue 153 } 154 if !multipleWorkers && (maxFlow-val.curFlow)*(time-val.timeToOpen) < bestGoal.f-val.f { 155 possibilityCheck++ 156 continue 157 } 158 if val.timeToOpen > time || openHash[val.hash] { 159 continue 160 } else { 161 cache[val.inPath] = val.f 162 open = append(open, val) 163 openHash[val.hash] = true 164 } 165 } 166 167 } 168 return bestGoal.f, bestPaths 169 } 170 171 func findBestDisjointPath(bestPaths map[int64]valve) (valve, valve) { 172 v1, v2 := valve{}, valve{} 173 for a, aVal := range bestPaths { 174 inner: 175 for b, bVal := range bestPaths { 176 if a == b { 177 continue 178 } 179 180 if aVal.inPath&bVal.inPath != 0 { 181 continue inner 182 } 183 if aVal.f+bVal.f > v1.f+v2.f { 184 v1 = aVal 185 v2 = bVal 186 } 187 } 188 } 189 return v1, v2 190 } 191 192 var cacheHit = 0 193 var possibilityCheck = 0 194 var overallPaths = 0 195 196 func Solve(input string, test bool, tasks int) (string, string) { 197 start := time.Now() 198 valves := parseValves(input) 199 if test { 200 for _, v := range valves { 201 fmt.Println(v) 202 } 203 } 204 parsing := time.Now() 205 206 ret1, ret2 := "", "" 207 // Task 1 208 if tasks == 0 || tasks == 1 { 209 maxPressure, _ := findMaxPressure(valves, 30, "AA", false) 210 fmt.Println("Part 1:") 211 fmt.Println("Cache hits:", cacheHit) 212 fmt.Println("Possibility checks:", possibilityCheck) 213 fmt.Println("Max Pressure:", maxPressure) 214 ret1 = strconv.Itoa(maxPressure) 215 } 216 task1 := time.Now() 217 218 // Task 2 219 if tasks == 0 || tasks == 2 { 220 cacheHit = 0 221 possibilityCheck = 0 222 _, bestPath := findMaxPressure(valves, 26, "AA", true) 223 fmt.Println("\n\nPart 2:") 224 fmt.Println("Cache hits:", cacheHit) 225 fmt.Println("Possibility checks:", possibilityCheck) 226 fmt.Println("Possible paths: ", len(bestPath)) 227 fmt.Println("Overall paths: ", overallPaths) 228 path1, path2 := findBestDisjointPath(bestPath) 229 if test { 230 fmt.Println("Opened paths:") 231 fmt.Println(" PATH 1: ", path1) 232 fmt.Println(" PATH 2: ", path2) 233 } 234 fmt.Println("Max Pressures with help: ", path1.f, path2.f) 235 fmt.Println("Sum: ", path1.f+path2.f) 236 fmt.Println(path1) 237 fmt.Println(path2) 238 ret2 = strconv.Itoa(path1.f + path2.f) 239 } 240 task2 := time.Now() 241 fmt.Println("Time: parsing ", parsing.Sub(start), " task1 ", task1.Sub(parsing), " task2 ", task2.Sub(task1), " total ", task2.Sub(start)) 242 243 return ret1, ret2 244 }