Compare commits

...

16 Commits

Author SHA1 Message Date
magical 5a7c8ef732 day 15 svg visualization 2022-12-17 16:36:01 -08:00
magical 032c1df356 day 16 reshuffle files 2022-12-17 15:29:48 -08:00
magical 64a09bb238 day 16 hide my shame 2022-12-17 15:29:32 -08:00
magical 6b0bf5d29b day 16 sample input 2022-12-17 15:26:04 -08:00
magical 2e18e87a95 lib/astar: fix return value for failed search 2022-12-17 15:22:18 -08:00
magical 043a57608d day 16 python part 2 solve with A*, and cleanup 2022-12-17 15:10:28 -08:00
magical bbc4075137 day 17 python tweaks 2022-12-17 13:31:57 -08:00
magical 8c1696fa32 day 16 speed up search by shrinking the graph 2022-12-17 13:20:30 -08:00
magical de987b60db day 17 python cleanup 2022-12-17 13:20:30 -08:00
magical 799fefa59a day 17 python part 2 solve 2022-12-17 13:20:30 -08:00
magical db649d4a56 day 17 python refactor 2022-12-17 13:20:20 -08:00
magical 40b9f86f4c day 17 part 1 python 2022-12-17 13:20:20 -08:00
magical a54304686d day 16 python alternate solution (astar) 2022-12-17 13:20:20 -08:00
magical 014b7daf09 day 16 part 2 python, finally
ugh
2022-12-17 13:20:20 -08:00
magical 03710f579a day 16 python part 1 solve
this shouldn't work
2022-12-17 13:19:18 -08:00
magical f39fbe1890 day 16 python attempt
works on the sample input, doesn't work on the real input
2022-12-17 13:18:50 -08:00
9 changed files with 686 additions and 4 deletions

25
day15/image.svg 100644
View 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 100644
View 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 100644
View 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 100644
View 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 100644
View 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 100644
View 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 100644

File diff suppressed because one or more lines are too long

122
day17/sol.py 100644
View 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)

View File

@ -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 = [