day 16 python alternate solution (astar)
parent
014b7daf09
commit
a54304686d
|
@ -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<<i for i, v in enumerate(V)} # bitmasks
|
||||||
|
E = {B[v]: [B[e] for e in edges] for v,_,edges in G} # edge[b] -> 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()
|
|
@ -1,10 +1,14 @@
|
||||||
from heapq import heappush, heappop
|
from heapq import heappush, heappop
|
||||||
|
|
||||||
def search(start, goal, neighbors, heuristic=None):
|
def search(start, is_goal, neighbors, heuristic=None):
|
||||||
if heuristic == None:
|
if heuristic == None:
|
||||||
def heuristic(x):
|
def heuristic(x):
|
||||||
return 0
|
return 0
|
||||||
# TODO: callable goal
|
# TODO: callable goal
|
||||||
|
if not callable(is_goal):
|
||||||
|
goal = is_goal
|
||||||
|
def is_goal(this):
|
||||||
|
return this == goal
|
||||||
if not isinstance(start, list):
|
if not isinstance(start, list):
|
||||||
start = [start]
|
start = [start]
|
||||||
i = 0 # tiebreaker
|
i = 0 # tiebreaker
|
||||||
|
@ -22,7 +26,7 @@ def search(start, goal, neighbors, heuristic=None):
|
||||||
|
|
||||||
done[this] = (cost_so_far, prev)
|
done[this] = (cost_so_far, prev)
|
||||||
|
|
||||||
if this == goal:
|
if is_goal(this):
|
||||||
print("astar: visited", len(done), "nodes")
|
print("astar: visited", len(done), "nodes")
|
||||||
# reconsruct the path
|
# reconsruct the path
|
||||||
n = this
|
n = this
|
||||||
|
|
Loading…
Reference in New Issue