Compare commits
16 Commits
7b40516b66
...
5a7c8ef732
Author | SHA1 | Date | |
---|---|---|---|
5a7c8ef732 | |||
032c1df356 | |||
64a09bb238 | |||
6b0bf5d29b | |||
2e18e87a95 | |||
043a57608d | |||
bbc4075137 | |||
8c1696fa32 | |||
de987b60db | |||
799fefa59a | |||
db649d4a56 | |||
40b9f86f4c | |||
a54304686d | |||
014b7daf09 | |||
03710f579a | |||
f39fbe1890 |
25
day15/image.svg
Normal file
25
day15/image.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<svg width="1000" height="1000" viewBox="0 0 4000000 4000000" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 325337 1156002L 1738198 2568863 L 325337 3981724 L -1087524 2568863 L 325337 1156002" style="fill:#ff0000;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3988825 -768301L 5594946 837820 L 3988825 2443941 L 2382704 837820 L 3988825 -768301" style="fill:#ff3700;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 1611311 363595L 3300890 2053174 L 1611311 3742753 L -78268 2053174 L 1611311 363595" style="fill:#ff6e00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 101890 2603932L 1438007 3940049 L 101890 5276166 L -1234227 3940049 L 101890 2603932" style="fill:#ffa600;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3962702 2247845L 4273282 2558425 L 3962702 2869005 L 3652122 2558425 L 3962702 2247845" style="fill:#ffdd00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 2957890 1448846L 3669857 2160813 L 2957890 2872780 L 2245923 2160813 L 2957890 1448846" style="fill:#e8ff00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3907456 3015138L 4217928 3325610 L 3907456 3636082 L 3596984 3325610 L 3907456 3015138" style="fill:#b1ff00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3354177 2884329L 3905767 3435919 L 3354177 3987509 L 2802587 3435919 L 3354177 2884329" style="fill:#79ff00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3997379 2616205L 4453042 3071868 L 3997379 3527531 L 3541716 3071868 L 3997379 2616205" style="fill:#42ff00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 145143 766120L 1093985 1714962 L 145143 2663804 L -803699 1714962 L 145143 766120" style="fill:#0bff00;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 611563 2496305L 1264122 3148864 L 611563 3801423 L -40996 3148864 L 611563 2496305" style="fill:#00ff2c;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3080405 2610557L 4374625 3904777 L 3080405 5198997 L 1786185 3904777 L 3080405 2610557" style="fill:#00ff63;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 644383 -574325L 1229440 10732 L 644383 595789 L 59326 10732 L 644383 -574325" style="fill:#00ff9b;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3229566 1177170L 3746563 1694167 L 3229566 2211164 L 2712569 1694167 L 3229566 1177170" style="fill:#00ffd2;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 1600637 2812349L 2773172 3984884 L 1600637 5157419 L 428102 3984884 L 1600637 2812349" style="fill:#00f3ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 2959765 2275581L 3505044 2820860 L 2959765 3366139 L 2414486 2820860 L 2959765 2275581" style="fill:#00bcff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 2235330 2641704L 3021423 3427797 L 2235330 4213890 L 1449237 3427797 L 2235330 2641704" style="fill:#0085ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 2428996 -1555978L 4195855 210881 L 2428996 1977740 L 662137 210881 L 2428996 -1555978" style="fill:#004dff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 369661 -299603L 1357069 687805 L 369661 1675213 L -617747 687805 L 369661 -299603" style="fill:#0016ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3558476 1372938L 4309152 2123614 L 3558476 2874290 L 2807800 2123614 L 3558476 1372938" style="fill:#2100ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3551529 2279143L 4097490 2825104 L 3551529 3371065 L 3005568 2825104 L 3551529 2279143" style="fill:#5800ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 64895 -594317L 662789 3577 L 64895 601471 L -532999 3577 L 64895 -594317" style="fill:#9000ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
<path d="M 3079531 1245503L 3372687 1538659 L 3079531 1831815 L 2786375 1538659 L 3079531 1245503" style="fill:#c700ff;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
26
day15/svg.py
Normal file
26
day15/svg.py
Normal file
@ -0,0 +1,26 @@
|
||||
data = []
|
||||
for line in open("input"):
|
||||
words = line.replace(":", "").replace(",","").split()
|
||||
coords = [int(w[w.index('=')+1:]) for w in words if '=' in w]
|
||||
data.append(coords)
|
||||
|
||||
import colorsys
|
||||
def hsl(h,s,l):
|
||||
r,g,b = colorsys.hls_to_rgb(h/360,l,s)
|
||||
return int(r*255),int(g*255),int(b*255)
|
||||
|
||||
colors = []
|
||||
for i in range(len(data)):
|
||||
hue = i*300/len(data)
|
||||
colors.append("#%02x%02x%02x" % hsl(hue, 1, .5))
|
||||
|
||||
print('<svg width="1000" height="1000" viewBox="0 0 4000000 4000000" xmlns="http://www.w3.org/2000/svg">')
|
||||
for i,(sx,sy,bx,by) in enumerate(data):
|
||||
dx = abs(sx-bx)
|
||||
dy = abs(sy-by)
|
||||
d = dx+dy
|
||||
path = [(sx+d,sy),(sx,sy+d),(sx-d,sy),(sx,sy-d)]
|
||||
s = "M %d %d" % (path[-1]) + " ".join("L %d %d" % (x,y) for x,y in path)
|
||||
print('<path d="%s" style="fill:%s;fill-opacity:0.5;stroke:#000;stroke-width:5000px" />' % (s,colors[i]))
|
||||
#print('<circle cx="3257428" cy="2573243" r="10000" fill="red" />')
|
||||
print('</svg>')
|
223
day16/bad.py
Normal file
223
day16/bad.py
Normal file
@ -0,0 +1,223 @@
|
||||
G = []
|
||||
for line in open("input"):
|
||||
words = line.split()
|
||||
valve = words[1]
|
||||
rate = words[4]
|
||||
edges = [x.strip(", ") for x in words[9:]]
|
||||
|
||||
print(rate)
|
||||
rate = rate.strip(";")
|
||||
rate = rate[rate.index("=")+1:]
|
||||
rate = int(rate)
|
||||
G.append((valve, rate, edges))
|
||||
|
||||
print(G)
|
||||
|
||||
def save():
|
||||
seen = set()
|
||||
with open("input.dot", "w") as f:
|
||||
print("graph {", file=f)
|
||||
for v, r, E in G:
|
||||
if r:
|
||||
print('%s [label="%s (%s)"]' % (v, v, r), file=f)
|
||||
for e in E:
|
||||
if (e,v) not in seen:
|
||||
print("%s -- %s" % (v,e), file=f)
|
||||
seen.add((v,e))
|
||||
print("}", file=f)
|
||||
|
||||
#save()
|
||||
|
||||
#import astar
|
||||
|
||||
def part1():
|
||||
score = {'AA': (0, 0, [])}
|
||||
|
||||
minutes = 30
|
||||
for _ in range(minutes):
|
||||
minutes -= 1
|
||||
next = {}
|
||||
for v, r, E in G:
|
||||
vo = v + 'o'
|
||||
s = []
|
||||
o = []
|
||||
if vo in score:
|
||||
reward, flow, open = score[vo]
|
||||
o.append((reward, flow, open))
|
||||
if v in score:
|
||||
reward, flow, open = score[v]
|
||||
# stay, don't open valve
|
||||
s.append((reward, flow, open))
|
||||
# stay in place, open valve
|
||||
if v not in open:
|
||||
reward += r*minutes
|
||||
o.append((reward, flow+r, open+[v]))
|
||||
# move here from somewhere else
|
||||
for e in E:
|
||||
if e in score:
|
||||
if v in score[e][-1]:
|
||||
o.append(score[e])
|
||||
else:
|
||||
s.append(score[e])
|
||||
eo = e+'o'
|
||||
if eo in score:
|
||||
if v in score[eo][-1]:
|
||||
o.append(score[eo])
|
||||
else:
|
||||
s.append(score[eo])
|
||||
if s:
|
||||
next[v] = max(s)
|
||||
if o:
|
||||
next[vo] = max(o)
|
||||
score = next
|
||||
print("%d minutes left" % minutes)
|
||||
for v, (r, flow, open) in sorted(score.items(), key=lambda x: x[1]):
|
||||
print("\t", v, r, "\t", ",".join(open))
|
||||
print(max(r for r,_,_ in score.values()))
|
||||
|
||||
def part2():
|
||||
H = {x[0]: x for x in G}
|
||||
def actions(v, open):
|
||||
_, r, E = H[v]
|
||||
if v in open:
|
||||
yield (v, 0, [])
|
||||
else:
|
||||
yield (v, 0, [])
|
||||
yield (v+'o', r, [v])
|
||||
# move to somewhere else
|
||||
for e in E:
|
||||
if e in open:
|
||||
e = e+'o'
|
||||
yield (e, 0, [])
|
||||
|
||||
score = {('AA', 'AA'): (0, 0, [])}
|
||||
minutes = 26
|
||||
for _ in range(minutes):
|
||||
next = {}
|
||||
minutes -= 1
|
||||
|
||||
for (me, elephant) in score:
|
||||
reward, flow, open = score[(me, elephant)]
|
||||
for v, r, o in actions(me.strip('o'), open):
|
||||
for v1, r1, o1 in actions(elephant.strip('o'), open+o):
|
||||
reward_ = reward + (r+r1)*minutes
|
||||
flow_ = flow + r + r1
|
||||
open_ = open + o + o1
|
||||
next.setdefault((v, v1), []).append((reward_, flow_, open_))
|
||||
|
||||
for k in next:
|
||||
next[k] = max(next[k])
|
||||
|
||||
score = next
|
||||
print("%d minutes left" % minutes)
|
||||
for v, (r, flow, open) in sorted(score.items(), key=lambda x: x[1]):
|
||||
print("\t", v, r, "\t", ",".join(open))
|
||||
print(max(r for r,_,_ in score.values()))
|
||||
|
||||
#part2()
|
||||
|
||||
def part2_b():
|
||||
score = {'AA': (0, 0, [])}
|
||||
|
||||
minutes = 26
|
||||
for _ in range(minutes):
|
||||
minutes -= 1
|
||||
next = {}
|
||||
for v, r, E in G:
|
||||
vo = v + 'o'
|
||||
s = []
|
||||
o = []
|
||||
if vo in score:
|
||||
reward, flow, open = score[vo]
|
||||
o.append((reward, flow, open))
|
||||
if v in score:
|
||||
reward, flow, open = score[v]
|
||||
# stay, don't open valve
|
||||
s.append((reward, flow, open))
|
||||
# stay in place, open valve
|
||||
if v not in open:
|
||||
reward += r*minutes
|
||||
o.append((reward, flow+r, open+[v]))
|
||||
# move here from somewhere else
|
||||
for e in E:
|
||||
if e in score:
|
||||
if v in score[e][-1]:
|
||||
o.append(score[e])
|
||||
else:
|
||||
s.append(score[e])
|
||||
eo = e+'o'
|
||||
if eo in score:
|
||||
if v in score[eo][-1]:
|
||||
o.append(score[eo])
|
||||
else:
|
||||
s.append(score[eo])
|
||||
if s:
|
||||
next[v] = max(s)
|
||||
if o:
|
||||
next[vo] = max(o)
|
||||
score = next
|
||||
print("%d minutes left" % minutes)
|
||||
for v, (r, flow, open) in sorted(score.items(), key=lambda x: x[1]):
|
||||
print("\t", v, r, "\t", ",".join(open))
|
||||
|
||||
maxpair = []
|
||||
for r,_,open in score.values():
|
||||
o = frozenset(open)
|
||||
for s,_,open in score.values():
|
||||
if o.isdisjoint(open):
|
||||
maxpair.append(r+s)
|
||||
print(max(maxpair))
|
||||
|
||||
#part2_b()
|
||||
|
||||
def part2_c():
|
||||
V = sorted(v for v,_,_ in G)
|
||||
B = {v: 1<<i for i, v in enumerate(V)}
|
||||
E = {B[v]: [B[e] for e in edges] for v,_,edges in G}
|
||||
R = {B[v]: r for v,r,_ in G}
|
||||
|
||||
all_open = sum(B.values())
|
||||
|
||||
aa = B['AA']
|
||||
states = [(aa, 0, 0)]
|
||||
minutes = 26
|
||||
for _ in range(minutes):
|
||||
print(minutes, len(states))
|
||||
minutes -= 1
|
||||
next = []
|
||||
seen = set()
|
||||
states.sort(key=lambda x: -x[2])
|
||||
for v, open, reward in states:
|
||||
if (v,open) in seen:
|
||||
continue
|
||||
seen.add((v,open))
|
||||
#next.append((v, open, reward))
|
||||
if open == all_open:
|
||||
next.append((v, open, reward))
|
||||
else:
|
||||
#next.append((v, open, reward))
|
||||
r = R[v]
|
||||
if r and not v&open:
|
||||
next.append((v, open|v, reward+r*minutes))
|
||||
for e in E[v]:
|
||||
next.append((e, open, reward))
|
||||
states = next
|
||||
|
||||
print(max(r for _,_,r in states))
|
||||
|
||||
best = {}
|
||||
for _,o,r in states:
|
||||
if o in best and best[o] >= r:
|
||||
continue
|
||||
best[o] = r
|
||||
|
||||
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()))
|
||||
|
||||
part2_c()
|
56
day16/input
Normal file
56
day16/input
Normal file
@ -0,0 +1,56 @@
|
||||
Valve JI has flow rate=21; tunnels lead to valves WI, XG
|
||||
Valve DM has flow rate=3; tunnels lead to valves JX, NG, AW, BY, PF
|
||||
Valve AZ has flow rate=0; tunnels lead to valves FJ, VC
|
||||
Valve YQ has flow rate=0; tunnels lead to valves TE, OP
|
||||
Valve WI has flow rate=0; tunnels lead to valves JI, VC
|
||||
Valve NE has flow rate=0; tunnels lead to valves ZK, AA
|
||||
Valve FM has flow rate=0; tunnels lead to valves LC, DU
|
||||
Valve QI has flow rate=0; tunnels lead to valves TE, JW
|
||||
Valve OY has flow rate=0; tunnels lead to valves XS, VF
|
||||
Valve XS has flow rate=18; tunnels lead to valves RR, OY, SV, NQ
|
||||
Valve NU has flow rate=0; tunnels lead to valves IZ, BD
|
||||
Valve JX has flow rate=0; tunnels lead to valves DM, ZK
|
||||
Valve WT has flow rate=23; tunnels lead to valves OV, QJ
|
||||
Valve KM has flow rate=0; tunnels lead to valves TE, OL
|
||||
Valve NG has flow rate=0; tunnels lead to valves II, DM
|
||||
Valve FJ has flow rate=0; tunnels lead to valves AZ, II
|
||||
Valve QR has flow rate=0; tunnels lead to valves ZK, KI
|
||||
Valve KI has flow rate=9; tunnels lead to valves ZZ, DI, TL, AJ, QR
|
||||
Valve ON has flow rate=0; tunnels lead to valves LC, QT
|
||||
Valve AW has flow rate=0; tunnels lead to valves DM, AA
|
||||
Valve HI has flow rate=0; tunnels lead to valves TE, VC
|
||||
Valve XG has flow rate=0; tunnels lead to valves II, JI
|
||||
Valve II has flow rate=19; tunnels lead to valves LF, NG, OL, FJ, XG
|
||||
Valve VC has flow rate=24; tunnels lead to valves WI, HI, AZ
|
||||
Valve VJ has flow rate=0; tunnels lead to valves UG, AA
|
||||
Valve IZ has flow rate=0; tunnels lead to valves VF, NU
|
||||
Valve EJ has flow rate=0; tunnels lead to valves ZK, LC
|
||||
Valve DU has flow rate=12; tunnels lead to valves TC, UG, FM
|
||||
Valve ZK has flow rate=10; tunnels lead to valves JX, EJ, JW, QR, NE
|
||||
Valve XF has flow rate=25; tunnels lead to valves OP, VT
|
||||
Valve LC has flow rate=4; tunnels lead to valves FM, EJ, ON, AJ, PF
|
||||
Valve SV has flow rate=0; tunnels lead to valves XS, IY
|
||||
Valve LF has flow rate=0; tunnels lead to valves II, OV
|
||||
Valve DI has flow rate=0; tunnels lead to valves KI, BY
|
||||
Valve OP has flow rate=0; tunnels lead to valves YQ, XF
|
||||
Valve NQ has flow rate=0; tunnels lead to valves TC, XS
|
||||
Valve QJ has flow rate=0; tunnels lead to valves VT, WT
|
||||
Valve IY has flow rate=22; tunnel leads to valve SV
|
||||
Valve AJ has flow rate=0; tunnels lead to valves LC, KI
|
||||
Valve TE has flow rate=11; tunnels lead to valves QI, HI, KM, YQ
|
||||
Valve ZZ has flow rate=0; tunnels lead to valves KI, AA
|
||||
Valve VT has flow rate=0; tunnels lead to valves XF, QJ
|
||||
Valve OL has flow rate=0; tunnels lead to valves KM, II
|
||||
Valve TC has flow rate=0; tunnels lead to valves NQ, DU
|
||||
Valve TL has flow rate=0; tunnels lead to valves VF, KI
|
||||
Valve QT has flow rate=0; tunnels lead to valves AA, ON
|
||||
Valve BY has flow rate=0; tunnels lead to valves DM, DI
|
||||
Valve OV has flow rate=0; tunnels lead to valves LF, WT
|
||||
Valve VN has flow rate=0; tunnels lead to valves RR, BD
|
||||
Valve VF has flow rate=13; tunnels lead to valves OY, IZ, TL
|
||||
Valve BD has flow rate=17; tunnels lead to valves NU, VN
|
||||
Valve UG has flow rate=0; tunnels lead to valves VJ, DU
|
||||
Valve PF has flow rate=0; tunnels lead to valves LC, DM
|
||||
Valve RR has flow rate=0; tunnels lead to valves XS, VN
|
||||
Valve AA has flow rate=0; tunnels lead to valves QT, ZZ, AW, VJ, NE
|
||||
Valve JW has flow rate=0; tunnels lead to valves ZK, QI
|
10
day16/sample
Normal file
10
day16/sample
Normal file
@ -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
|
216
day16/sol.py
Normal file
216
day16/sol.py
Normal file
@ -0,0 +1,216 @@
|
||||
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 solve():
|
||||
G.sort(key=lambda x: (-x[1],x[0]))
|
||||
B = {v: 1<<i for i,(v,_,_) in enumerate(G)} # bitmasks
|
||||
E = {B[v]: [B[e] for e in edges] for v,_,edges in G} # E[b] = edges of b
|
||||
R = {B[v]: r for v,r,_ in G if r} # R[b] -> rate
|
||||
|
||||
AA = B['AA']
|
||||
|
||||
all_closed = sum(R.keys())
|
||||
all_open = 0
|
||||
|
||||
# TODO: memoize this
|
||||
def pressure(bits):
|
||||
pressure = 0
|
||||
for v,r in R.items():
|
||||
if bits&v:
|
||||
pressure += r
|
||||
return pressure
|
||||
|
||||
assert pressure(all_open) == 0
|
||||
max_pressure = pressure(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 use that to build a
|
||||
# weighted graph which only has paths from any room to rooms with a valve.
|
||||
#
|
||||
# not only does this make our search space smaller,
|
||||
# it also helps by making it so that the cost changes on every step
|
||||
# (since opening a valve is the only thing that actually changes the pressure)
|
||||
# giving A* a much clearer signal about which paths are worth exploring
|
||||
dist = {}
|
||||
for v in E:
|
||||
dist[v,v] = 0
|
||||
for e in E[v]:
|
||||
dist[v,e] = 1
|
||||
dist[e,v] = 1
|
||||
for t in E:
|
||||
for u in E:
|
||||
for v in E:
|
||||
if (u,t) in dist and (t,v) in dist:
|
||||
dist[u,v] = min(dist.get((u,v),999999), dist[u,t] + dist[t,v])
|
||||
|
||||
W = {} # weighted edges
|
||||
for u in E:
|
||||
W[u] = []
|
||||
for v in R:
|
||||
if (u,v) in dist:
|
||||
W[u].append((v, dist[u,v]))
|
||||
|
||||
print(W[AA])
|
||||
|
||||
# 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)
|
||||
#
|
||||
# we can keep doing that until there are no valves left to open
|
||||
# or there is no time left.
|
||||
#
|
||||
# note that the nodes with the largest flow rate are assigned
|
||||
# the lowest position in the bitmap, so clearing the bits from
|
||||
# low to high will always give us the optimal order
|
||||
def heuristic(node):
|
||||
v, minutes, closed = node
|
||||
# assume we can open a valve every 2 minutes
|
||||
# how much would that cost?
|
||||
c = 0
|
||||
while closed and minutes > 0:
|
||||
c += pressure(closed) * min(minutes,2)
|
||||
closed &= (closed-1)
|
||||
minutes -= 2
|
||||
return c
|
||||
|
||||
def is_goal(node):
|
||||
v, minutes, closed = node
|
||||
return minutes == 0 or closed == all_open
|
||||
|
||||
info = {}
|
||||
def neighbors(node):
|
||||
v, minutes, closed = node
|
||||
if minutes not in info:
|
||||
print(info)
|
||||
info[minutes] = 0
|
||||
info[minutes] += 1
|
||||
if minutes <= 0:
|
||||
pass
|
||||
elif closed == all_open:
|
||||
yield 0, (v, 0, closed)
|
||||
else:
|
||||
# move to a closed valve (or maybe stay in
|
||||
# the same spot) and open it
|
||||
can_move = False
|
||||
for e, dist in W[v]:
|
||||
t = dist + 1
|
||||
if e&closed and t <= minutes:
|
||||
can_move = True
|
||||
c = pressure(closed)*t
|
||||
yield c, (e, minutes-t, closed&~e)
|
||||
# wait til the end
|
||||
if can_move == False:
|
||||
yield pressure(closed)*minutes, (v, 0, closed)
|
||||
|
||||
minutes = 30
|
||||
start = (AA, minutes, all_closed)
|
||||
|
||||
c, _, path = astar.search(start, is_goal, neighbors, heuristic)
|
||||
print(c)
|
||||
print(max_pressure*minutes - c)
|
||||
|
||||
def heuristic2(node):
|
||||
if is_goal2(node):
|
||||
return 0
|
||||
v1, v2, min1, min2, closed = node
|
||||
# if the players are out of sync, assume the other player
|
||||
# will close one valve when they catch up
|
||||
if min1 != min2:
|
||||
closed &= closed - 1
|
||||
# assume we can open a valve every minute remaining
|
||||
# how much would that cost?
|
||||
c = 0
|
||||
pr = pressure(closed)
|
||||
m = min(min1, min2)
|
||||
while closed and m > 0:
|
||||
c += pr
|
||||
tmp = closed
|
||||
closed &= (closed-1)
|
||||
pr -= R[tmp-closed]
|
||||
m -= 1
|
||||
return c
|
||||
|
||||
def is_goal2(node):
|
||||
_, _, min1, min2, closed = node
|
||||
return min1 == 0 and min2 == 0 or closed == all_open
|
||||
|
||||
def neighbors2(node):
|
||||
v1, v2, min1, min2, closed = node
|
||||
|
||||
if min(min1,min2) not in info:
|
||||
print(info)
|
||||
info.setdefault(min1, 0)
|
||||
info.setdefault(min2, 0)
|
||||
info[min1] += 1
|
||||
info[min2] += 1
|
||||
|
||||
if min1 <= 0 and min2 <= 0:
|
||||
pass
|
||||
elif closed == all_open:
|
||||
yield 0, (v1, v2, 0, 0, closed)
|
||||
else:
|
||||
moved = False
|
||||
# either player can move
|
||||
# but we can't open a valve that would take less time to open
|
||||
# than the other player has already used. we've already paid
|
||||
# the cost for _not_ opening those valves and we can't
|
||||
# retroactively change that (no time travel)
|
||||
|
||||
# if both players are in the same spot and have the same amount of
|
||||
# time remaining, then only let one of them move in order to break
|
||||
# symmetries (this can only happen in the start state)
|
||||
|
||||
# TODO: are there more symmetries we can break?
|
||||
|
||||
# move to a closed valve and open it
|
||||
discount1 = max(min1-min2, 0)
|
||||
for e, dist in W[v1]:
|
||||
t = dist + 1
|
||||
if e&closed and discount1 <= t <= min1:
|
||||
moved = True
|
||||
c = pressure(closed)*(t-discount1)
|
||||
yield c, (e, v2, min1-t, min2, closed&~e)
|
||||
if (v1, min1) != (v2, min2):
|
||||
discount2 = max(min2-min1, 0)
|
||||
for e, dist in W[v2]:
|
||||
t = dist + 1
|
||||
if e&closed and discount2 <= t <= min2:
|
||||
moved = True
|
||||
c = pressure(closed)*(t-discount2)
|
||||
yield c, (v1, e, min1, min2-t, closed&~e)
|
||||
|
||||
# are there no moves left?
|
||||
# then wait out the timer
|
||||
if not moved:
|
||||
yield pressure(closed)*min(min1,min2), (v1, v2, 0, 0, closed)
|
||||
|
||||
minutes = 26
|
||||
start2 = (AA, AA, minutes, minutes, all_closed)
|
||||
info.clear()
|
||||
c, _, path = astar.search(start2, is_goal2, neighbors2, heuristic2)
|
||||
print(c)
|
||||
print(max_pressure*minutes - c)
|
||||
|
||||
solve()
|
1
day17/input
Normal file
1
day17/input
Normal file
File diff suppressed because one or more lines are too long
122
day17/sol.py
Normal file
122
day17/sol.py
Normal file
@ -0,0 +1,122 @@
|
||||
import itertools
|
||||
rocks = [
|
||||
0b00011110,
|
||||
0b00001000_00011100_00001000,
|
||||
0b00000100_00000100_00011100,
|
||||
0b00010000_00010000_00010000_00010000,
|
||||
0b00011000_00011000,
|
||||
]
|
||||
|
||||
wall = 0b10000000_10000000_10000000_10000000_10000000
|
||||
|
||||
jets = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"
|
||||
jets = open("input").read().strip()
|
||||
|
||||
|
||||
def ceil(n):
|
||||
if n%8 != 0:
|
||||
n += 8 - n%8
|
||||
return n
|
||||
|
||||
def drop(a, r, jets):
|
||||
h = ceil(a.bit_length()) + 8*3
|
||||
numjets = 0
|
||||
while True:
|
||||
# push left or right
|
||||
j = next(jets)
|
||||
if j == '<':
|
||||
x = (r<<1)
|
||||
if x & wall == 0:
|
||||
if x & (a>>h) == 0:
|
||||
r = x
|
||||
elif j == '>':
|
||||
x = (r<<7)
|
||||
if x & wall == 0:
|
||||
x >>= 8
|
||||
if x & (a>>h) == 0:
|
||||
r = x
|
||||
numjets += 1
|
||||
|
||||
# continue falling?
|
||||
if r & (a>>(h-8)) == 0:
|
||||
h -= 8
|
||||
assert h > 0
|
||||
else:
|
||||
# stop
|
||||
a |= (r<<h)
|
||||
return a, numjets
|
||||
|
||||
def head(a):
|
||||
# technically this isn't sufficient.
|
||||
# you could imagine a stack of overhangs like
|
||||
#
|
||||
# |# #|
|
||||
# |#### #|
|
||||
# |# #|
|
||||
# |# #|
|
||||
# |# ###|
|
||||
# |# #|
|
||||
#
|
||||
# that a rock might be able to navigate through indefinitely,
|
||||
# but our stacks are much messier than that, so no need to worry.
|
||||
#
|
||||
# we could probably just take the top 10 or so rows and call it good.
|
||||
h = ceil(a.bit_length())
|
||||
x = 0x80
|
||||
while h >= 8:
|
||||
h -= 8
|
||||
x |= (a>>h)&0xff
|
||||
if x == 0xff:
|
||||
return a>>h
|
||||
return a
|
||||
|
||||
def height(a):
|
||||
return ceil(a.bit_length())//8
|
||||
|
||||
|
||||
# Part 1
|
||||
a = 0b11111111
|
||||
jeti = itertools.cycle(jets)
|
||||
rocki = itertools.cycle(rocks)
|
||||
for _ in range(2022):
|
||||
r = next(rocki)
|
||||
a, _ = drop(a,r,jeti)
|
||||
print(height(a>>8))
|
||||
|
||||
|
||||
# Part 2
|
||||
a = 0b11111111
|
||||
stopped = 0
|
||||
jeti = itertools.cycle(jets)
|
||||
rocki = itertools.cycle(rocks)
|
||||
i = 0
|
||||
j = 0
|
||||
seen = dict()
|
||||
|
||||
try:
|
||||
while True:
|
||||
r = next(rocki)
|
||||
a, n = drop(a,r,jeti)
|
||||
stopped += 1
|
||||
i = (i+1) % len(rocks)
|
||||
j = (j+n) % len(jets)
|
||||
if (i,j,head(a)) in seen:
|
||||
break
|
||||
seen[(i,j,head(a))] = (a,stopped)
|
||||
#for i in reversed(range(0,a.bit_length(),8)):
|
||||
# print("{:08b}".format((a>>i)&0xff))
|
||||
#print()
|
||||
except:
|
||||
print(i, j, stopped)
|
||||
raise
|
||||
|
||||
b, old = seen[(i,j,head(a))]
|
||||
sdiff = stopped - old
|
||||
hdiff = height(a) - height(b)
|
||||
|
||||
goal = 10**12
|
||||
rounds, extra = divmod(goal - stopped, sdiff)
|
||||
for _ in range(extra):
|
||||
r = next(rocki)
|
||||
a, n = drop(a,r,jeti)
|
||||
print(rounds*hdiff + height(a)-1)
|
11
lib/astar.py
11
lib/astar.py
@ -1,10 +1,13 @@
|
||||
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 +25,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
|
||||
@ -40,7 +43,7 @@ def search(start, goal, neighbors, heuristic=None):
|
||||
i += 1
|
||||
heappush(q, (c, i, n, this))
|
||||
|
||||
return float('inf'), None
|
||||
return float('inf'), None, []
|
||||
|
||||
def test():
|
||||
data = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user