diff --git a/day16/search.py b/day16/search.py new file mode 100644 index 0000000..552d76e --- /dev/null +++ b/day16/search.py @@ -0,0 +1,97 @@ +G = [] +for line in open("input"): + words = line.split() + valve = words[1] + rate = int(''.join(x for x in words[4] if x.isdigit())) + edges = [x.strip(", ") for x in words[9:]] + G.append((valve, rate, edges)) + +#print(G) + +import sys, os; sys.path.append(os.path.join(os.path.dirname(__file__), "../lib")) +import astar + +def search(): + V = sorted(v for v,_,_ in G) # vertices + B = {v: 1< b + R = {B[v]: r for v,r,_ in G} # rewards: R[b] = reward + + all_closed = sum(B.values()) + all_open = 0 + + minutes = 30 + start = (B['AA'], minutes, all_closed) + + # A* search minimizes costs + # it can't maxmize anything + # so we'll borrow an idea from https://github.com/morgoth1145/advent-of-code/blob/2bf7c157e37b3e0a65deedc6c88e42297d813d1d/2022/16/solution.py + # and instead say that the cost of moving from one node to the next + # is equal to the potential pressure we could have released from the closed pipes + # or, in other words, we'll keep track of how much pressure builds up in + # closed pipes instead of how much pressure is released from open pipes + + # let's shink the graph by finding the shortest path between + # every pair of rooms (floyd-warshall), and then building a graph which only has + # paths from the starting room to rooms with a valve + # and from any room with a valve to any other room with a valve + + # our heuristic cost has to be <= the actual cost of getting to the goal + # here's a simple one: + # we know it takes at least 1 minute to open a valve, + # and at least another minute to walk to the valve + # so we can assign at least cost 2*pressure(closed_valves) to this node + # (unless there is only 1 minute left) + def heuristic(node): + v, minutes, closed = node + if closed == all_open: + m = minutes + else: + m = min(minutes,1) + return m*pressure(closed) + + def pressure(bits): + pressure = 0 + for v,r in R.items(): + if bits&v: + pressure += r + return pressure + + def is_goal(n): + v, minutes, closed = n + return minutes == 0 + + info = {} + def neighbors(n): + v, minutes, closed = n + if minutes not in info: + print(info) + info[minutes] = 0 + info[minutes] += 1 + if minutes <= 0: + pass + elif closed == all_open: + c = pressure(closed) * minutes + yield c, (v, 0, closed) + else: + c = pressure(closed) + if v&closed and R[v]: + yield c, (v, minutes-1, closed & ~v) + for e in E[v]: + yield c, (e, minutes-1, closed) + + + c, _, path = astar.search(start, is_goal, neighbors, heuristic) + print(c) + print(pressure(all_closed)*30 - c) + + #maxpair = [] + #def pairs(): + # O = sorted(best.keys()) + # for i in range(len(O)): + # for j in range(i,len(O)): + # if not O[i] & O[j]: + # yield(best[O[i]]+best[O[j]]) + #print(max(pairs())) + +search() diff --git a/lib/astar.py b/lib/astar.py index 55d3634..d5ab027 100644 --- a/lib/astar.py +++ b/lib/astar.py @@ -1,10 +1,14 @@ from heapq import heappush, heappop -def search(start, goal, neighbors, heuristic=None): +def search(start, is_goal, neighbors, heuristic=None): if heuristic == None: def heuristic(x): return 0 # TODO: callable goal + if not callable(is_goal): + goal = is_goal + def is_goal(this): + return this == goal if not isinstance(start, list): start = [start] i = 0 # tiebreaker @@ -22,7 +26,7 @@ def search(start, goal, neighbors, heuristic=None): done[this] = (cost_so_far, prev) - if this == goal: + if is_goal(this): print("astar: visited", len(done), "nodes") # reconsruct the path n = this