Skip to content

Commit 53f4254

Browse files
committed
Add day 16
1 parent fc40d95 commit 53f4254

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

2022/day16/aoc_tools.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from collections import deque, defaultdict, Counter
2+
import itertools
3+
import re
4+
from typing import TypeVar, Generator, Iterable, Tuple, List
5+
6+
_T = TypeVar("T")
7+
8+
def adjacent_pairs(elements: Iterable[_T]) -> Generator[Tuple[_T, _T], None, None]:
9+
elements_iter = iter(elements)
10+
last_element = next(elements_iter)
11+
for element in elements_iter:
12+
yield (last_element, element)
13+
last_element = element
14+
15+
def all_pairs(elements: Iterable[_T]) -> Generator[Tuple[_T, _T], None, None]:
16+
elements_list = list(elements)
17+
for i in range(len(elements_list)):
18+
for j in range(i + 1, len(elements_list)):
19+
yield (elements_list[i], elements_list[j])
20+
21+
def all_tuples(elements: Iterable[_T]) -> Generator[Tuple[_T, _T], None, None]:
22+
elements_list = list(elements)
23+
for i in range(len(elements_list)):
24+
for j in range(len(elements_list)):
25+
if j == i: continue
26+
yield (elements_list[i], elements_list[j])
27+
28+
29+
# yes, everyone else calls this "cumsum" but ohwell
30+
def rolling_sum(elements: Iterable[_T], start: _T = None) -> List[_T]:
31+
rsum = []
32+
elements_iter = iter(elements)
33+
34+
if start is None:
35+
rsum.append(next(elements_iter))
36+
else:
37+
rsum.append(start)
38+
39+
for element in elements_iter:
40+
rsum.append(rsum[-1] + element)
41+
return rsum
42+
43+
44+
# I did some rough experiments with this version vs. a version that uses a deque, which
45+
# has efficient popleft, and it seems like this version actually wins because of how slow
46+
# iterating over a deque is (which you have to do if you want to use the results)
47+
def rolling_window(
48+
elements: Iterable[_T],
49+
window_size: int,
50+
) -> Generator[Tuple[_T, ...], None, None]:
51+
current_window = []
52+
for element in elements:
53+
current_window.append(element)
54+
if len(current_window) > window_size:
55+
del current_window[0]
56+
if len(current_window) == window_size:
57+
yield current_window
58+
59+
60+
61+
# this is like slightly borked because it doesn't get negative numbers
62+
# oh well i guess
63+
#nums_regex = regex.compile("([^\\d]*)((?P<nums>\\d+)([^\\d]*))*")
64+
65+
def nums(s):
66+
m = nums_regex.match(s)
67+
vals = m.capturesdict()["nums"]
68+
return [int(x) for x in vals]
69+
70+
def nums(s):
71+
m = re.findall("-?\d+", s)
72+
return [int(x) for x in m]
73+
74+
def numsp(s):
75+
m = re.findall("-?\d+", s)
76+
return [int(x) for x in m]
77+
78+
def sign(x):
79+
if x < 0:
80+
return -1
81+
elif x == 0:
82+
return 0
83+
else:
84+
return 1
85+
86+
87+
# underscored names are in case functions get shadowed by accident
88+
adjp = _adjp = adjacent_pairs
89+
ap = _ap = all_pairs
90+
at = _at = all_tuples
91+
rw = _rw = rolling_window
92+
rsum = _rsum = rolling_sum
93+
94+
dd = _dd = defaultdict
95+
ctr = _ctr = Counter

2022/day16/day16.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import string
2+
from collections import defaultdict
3+
from aoc_tools import *
4+
import functools
5+
dirs = [(0,1),(1,0),(0,-1),(-1,0)]
6+
7+
with open(r"input.txt") as f:
8+
s = f.read().strip()
9+
print("\n".join(x[:60] for x in s.split("\n")[:6]))
10+
11+
g = {}
12+
f = {}
13+
for line in s.split("\n"):
14+
valve = line[6:8]
15+
flow = nums(line)[0]
16+
_, r = line.split(";")
17+
r = r.replace("valves","valve")[len(" tunnels lead to valve "):]
18+
g[valve] = r.split(", ")
19+
f[valve] = flow
20+
21+
cur = "AA"
22+
23+
@functools.lru_cache(maxsize=None)
24+
def maxflow(cur, opened, min_left):
25+
if min_left <= 0:
26+
return 0
27+
best = 0
28+
if cur not in opened:
29+
val = (min_left - 1) * f[cur]
30+
cur_opened = tuple(sorted(opened + (cur,)))
31+
for adj in g[cur]:
32+
if val != 0:
33+
best = max(best,
34+
val + maxflow(adj, cur_opened, min_left - 2))
35+
best = max(best,
36+
maxflow(adj, opened, min_left - 1))
37+
return best
38+
39+
print(maxflow("AA", (), 30))
40+
41+
42+
43+
# -> (largest_min_left, result)
44+
##cache = {}
45+
##def maxflow2(cur, cur2, opened, min_left):
46+
## if min_left <= 0:
47+
## return 0
48+
## if (cur, cur2, opened) not in cache or min_left > cache[cur, cur2, opened][0]:
49+
## if min_left >= 13:
50+
## print(min_left)
51+
## best = 0
52+
## # open,open
53+
## # move,open
54+
## # open,move
55+
## # move,move
56+
##
57+
## if cur not in opened and f[cur] > 0:
58+
## if cur2 not in opened and f[cur2] > 0:
59+
## # consider open,open
60+
## val = (min_left - 1) * (f[cur] + f[cur2])
61+
## new_opened = tuple(sorted(opened + (cur, cur2)))
62+
## best = max(best, val + maxflow2(cur, cur2, new_opened, min_left - 1))
63+
##
64+
## # consider open,move
65+
## val = (min_left - 1) * f[cur]
66+
## new_opened = tuple(sorted(opened + (cur,)))
67+
## for adj in g[cur2]:
68+
## best = max(best, val + maxflow2(cur, adj, new_opened, min_left - 1))
69+
##
70+
## if cur2 not in opened and f[cur2] > 0:
71+
## # consider move,open
72+
## val = (min_left - 1) * f[cur2]
73+
## new_opened = tuple(sorted(opened + (cur2,)))
74+
## for adj in g[cur]:
75+
## best = max(best, val + maxflow2(adj, cur2, new_opened, min_left - 1))
76+
##
77+
## # consider move,move
78+
## for adj1 in g[cur]:
79+
## for adj2 in g[cur2]:
80+
## best = max(best, maxflow2(adj1, adj2, opened, min_left - 1))
81+
##
82+
## cache[cur, cur2, opened] = (min_left, best)
83+
## return cache[cur, cur2, opened][1]
84+
##
85+
###print(maxflow("AA", (), 30))
86+
##print(maxflow2("AA", "AA", (), 26))
87+
88+
89+
dist2 = {('OT', 'IS'): 10, ('OT', 'WI'): 7, ('OT', 'QQ'): 7, ('OT', 'ZL'): 4, ('OT', 'OM'): 4, ('OT', 'NG'): 7, ('OT', 'AA'): 3, ('OT', 'YW'): 2, ('OT', 'DG'): 10, ('OT', 'MX'): 5, ('OT', 'HV'): 2, ('OT', 'GB'): 2, ('OT', 'IC'): 2, ('OT', 'VX'): 7, ('OT', 'FM'): 10, ('IS', 'OT'): 10, ('IS', 'WI'): 9, ('IS', 'QQ'): 3, ('IS', 'ZL'): 6, ('IS', 'OM'): 6, ('IS', 'NG'): 3, ('IS', 'AA'): 10, ('IS', 'YW'): 8, ('IS', 'DG'): 2, ('IS', 'MX'): 5, ('IS', 'HV'): 8, ('IS', 'GB'): 10, ('IS', 'IC'): 8, ('IS', 'VX'): 9, ('IS', 'FM'): 6, ('WI', 'OT'): 7, ('WI', 'IS'): 9, ('WI', 'QQ'): 12, ('WI', 'ZL'): 3, ('WI', 'OM'): 6, ('WI', 'NG'): 6, ('WI', 'AA'): 7, ('WI', 'YW'): 5, ('WI', 'DG'): 11, ('WI', 'MX'): 10, ('WI', 'HV'): 7, ('WI', 'GB'): 7, ('WI', 'IC'): 5, ('WI', 'VX'): 3, ('WI', 'FM'): 15, ('QQ', 'OT'): 7, ('QQ', 'IS'): 3, ('QQ', 'WI'): 12, ('QQ', 'ZL'): 9, ('QQ', 'OM'): 9, ('QQ', 'NG'): 6, ('QQ', 'AA'): 8, ('QQ', 'YW'): 9, ('QQ', 'DG'): 3, ('QQ', 'MX'): 2, ('QQ', 'HV'): 5, ('QQ', 'GB'): 7, ('QQ', 'IC'): 7, ('QQ', 'VX'): 12, ('QQ', 'FM'): 3, ('ZL', 'OT'): 4, ('ZL', 'IS'): 6, ('ZL', 'WI'): 3, ('ZL', 'QQ'): 9, ('ZL', 'OM'): 3, ('ZL', 'NG'): 3, ('ZL', 'AA'): 4, ('ZL', 'YW'): 2, ('ZL', 'DG'): 8, ('ZL', 'MX'): 7, ('ZL', 'HV'): 4, ('ZL', 'GB'): 4, ('ZL', 'IC'): 2, ('ZL', 'VX'): 6, ('ZL', 'FM'): 12, ('OM', 'OT'): 4, ('OM', 'IS'): 6, ('OM', 'WI'): 6, ('OM', 'QQ'): 9, ('OM', 'ZL'): 3, ('OM', 'NG'): 3, ('OM', 'AA'): 5, ('OM', 'YW'): 2, ('OM', 'DG'): 8, ('OM', 'MX'): 9, ('OM', 'HV'): 6, ('OM', 'GB'): 4, ('OM', 'IC'): 5, ('OM', 'VX'): 3, ('OM', 'FM'): 12, ('NG', 'OT'): 7, ('NG', 'IS'): 3, ('NG', 'WI'): 6, ('NG', 'QQ'): 6, ('NG', 'ZL'): 3, ('NG', 'OM'): 3, ('NG', 'AA'): 7, ('NG', 'YW'): 5, ('NG', 'DG'): 5, ('NG', 'MX'): 8, ('NG', 'HV'): 7, ('NG', 'GB'): 7, ('NG', 'IC'): 5, ('NG', 'VX'): 6, ('NG', 'FM'): 9, ('AA', 'OT'): 3, ('AA', 'IS'): 10, ('AA', 'WI'): 7, ('AA', 'QQ'): 8, ('AA', 'ZL'): 4, ('AA', 'OM'): 5, ('AA', 'NG'): 7, ('AA', 'YW'): 3, ('AA', 'DG'): 11, ('AA', 'MX'): 6, ('AA', 'HV'): 3, ('AA', 'GB'): 3, ('AA', 'IC'): 2, ('AA', 'VX'): 8, ('AA', 'FM'): 11, ('YW', 'OT'): 2, ('YW', 'IS'): 8, ('YW', 'WI'): 5, ('YW', 'QQ'): 9, ('YW', 'ZL'): 2, ('YW', 'OM'): 2, ('YW', 'NG'): 5, ('YW', 'AA'): 3, ('YW', 'DG'): 10, ('YW', 'MX'): 7, ('YW', 'HV'): 4, ('YW', 'GB'): 2, ('YW', 'IC'): 4, ('YW', 'VX'): 5, ('YW', 'FM'): 12, ('DG', 'OT'): 10, ('DG', 'IS'): 2, ('DG', 'WI'): 11, ('DG', 'QQ'): 3, ('DG', 'ZL'): 8, ('DG', 'OM'): 8, ('DG', 'NG'): 5, ('DG', 'AA'): 11, ('DG', 'YW'): 10, ('DG', 'MX'): 5, ('DG', 'HV'): 8, ('DG', 'GB'): 10, ('DG', 'IC'): 10, ('DG', 'VX'): 11, ('DG', 'FM'): 6, ('MX', 'OT'): 5, ('MX', 'IS'): 5, ('MX', 'WI'): 10, ('MX', 'QQ'): 2, ('MX', 'ZL'): 7, ('MX', 'OM'): 9, ('MX', 'NG'): 8, ('MX', 'AA'): 6, ('MX', 'YW'): 7, ('MX', 'DG'): 5, ('MX', 'HV'): 3, ('MX', 'GB'): 5, ('MX', 'IC'): 5, ('MX', 'VX'): 12, ('MX', 'FM'): 5, ('HV', 'OT'): 2, ('HV', 'IS'): 8, ('HV', 'WI'): 7, ('HV', 'QQ'): 5, ('HV', 'ZL'): 4, ('HV', 'OM'): 6, ('HV', 'NG'): 7, ('HV', 'AA'): 3, ('HV', 'YW'): 4, ('HV', 'DG'): 8, ('HV', 'MX'): 3, ('HV', 'GB'): 2, ('HV', 'IC'): 2, ('HV', 'VX'): 9, ('HV', 'FM'): 8, ('GB', 'OT'): 2, ('GB', 'IS'): 10, ('GB', 'WI'): 7, ('GB', 'QQ'): 7, ('GB', 'ZL'): 4, ('GB', 'OM'): 4, ('GB', 'NG'): 7, ('GB', 'AA'): 3, ('GB', 'YW'): 2, ('GB', 'DG'): 10, ('GB', 'MX'): 5, ('GB', 'HV'): 2, ('GB', 'IC'): 2, ('GB', 'VX'): 7, ('GB', 'FM'): 10, ('IC', 'OT'): 2, ('IC', 'IS'): 8, ('IC', 'WI'): 5, ('IC', 'QQ'): 7, ('IC', 'ZL'): 2, ('IC', 'OM'): 5, ('IC', 'NG'): 5, ('IC', 'AA'): 2, ('IC', 'YW'): 4, ('IC', 'DG'): 10, ('IC', 'MX'): 5, ('IC', 'HV'): 2, ('IC', 'GB'): 2, ('IC', 'VX'): 8, ('IC', 'FM'): 10, ('VX', 'OT'): 7, ('VX', 'IS'): 9, ('VX', 'WI'): 3, ('VX', 'QQ'): 12, ('VX', 'ZL'): 6, ('VX', 'OM'): 3, ('VX', 'NG'): 6, ('VX', 'AA'): 8, ('VX', 'YW'): 5, ('VX', 'DG'): 11, ('VX', 'MX'): 12, ('VX', 'HV'): 9, ('VX', 'GB'): 7, ('VX', 'IC'): 8, ('VX', 'FM'): 15, ('FM', 'OT'): 10, ('FM', 'IS'): 6, ('FM', 'WI'): 15, ('FM', 'QQ'): 3, ('FM', 'ZL'): 12, ('FM', 'OM'): 12, ('FM', 'NG'): 9, ('FM', 'AA'): 11, ('FM', 'YW'): 12, ('FM', 'DG'): 6, ('FM', 'MX'): 5, ('FM', 'HV'): 8, ('FM', 'GB'): 10, ('FM', 'IC'): 10, ('FM', 'VX'): 15}
90+
91+
g2 = defaultdict(list)
92+
93+
keep = ['OT', 'IS', 'WI', 'QQ', 'ZL', 'OM', 'NG', 'AA', 'YW', 'DG', 'MX', 'HV', 'GB', 'IC', 'VX', 'FM']
94+
95+
for x in keep:
96+
for y in keep:
97+
if x == y: continue
98+
g2[x].append((dist2[x,y], y))
99+
100+
# dp/memo
101+
# (our_loc, our_time_left, ele_loc, ele_time_left, opened_valves)
102+
103+
def add(open_valves, new_valve):
104+
return tuple(sorted(open_valves + (new_valve,)))
105+
106+
# no work :-(
107+
##@functools.lru_cache(maxsize=None)
108+
##def solve2(our_loc, our_time_left, ele_loc, ele_time_left, open_valves):
109+
## if our_time_left + ele_time_left > 30:
110+
## print(our_time_left, ele_time_left)
111+
## # us move, us open, ele move, ele open
112+
## best = 0
113+
##
114+
## for cost,adj in g2[our_loc]:
115+
## if our_time_left >= cost + 2: # BAD HEURISTIC
116+
## best = max(best, solve2(adj, our_time_left - cost,
117+
## ele_loc, ele_time_left, open_valves))
118+
##
119+
## if our_time_left >= 2 and our_loc not in open_valves:
120+
## best = max(best, (our_time_left - 1) * f[our_loc] + solve2(
121+
## our_loc, our_time_left - 1, ele_loc, ele_time_left, add(open_valves, our_loc)
122+
## ))
123+
##
124+
## for cost,adj in g2[ele_loc]:
125+
## if ele_time_left >= cost + 2: # BAD HEURISTIC
126+
## best = max(best, solve2(our_loc, our_time_left,
127+
## adj, ele_time_left - cost, open_valves))
128+
##
129+
## if ele_time_left >= 2 and ele_loc not in open_valves:
130+
## best = max(best, (ele_time_left - 1) * f[ele_loc] + solve2(
131+
## our_loc, our_time_left, ele_loc, ele_time_left - 1, add(open_valves, ele_loc)
132+
## ))
133+
##
134+
## return best
135+
136+
for cost,adj in sorted(g2["AA"]):
137+
print(cost,adj,f[adj])
138+
139+
# paths where we activate each stop
140+
def get_paths(loc, budget, exclude=None):
141+
if exclude is None: exclude = set()
142+
if budget >= 1:
143+
yield (loc,)
144+
for cost, adj in g2[loc]:
145+
if adj in exclude: continue
146+
if budget >= cost + 2:
147+
for path in get_paths(adj, budget - cost - 1, exclude | {loc}):
148+
yield (loc,) + path
149+
150+
@functools.lru_cache(maxsize=None)
151+
def value(path, time):
152+
result = 0
153+
for a,b in zip(path, path[1:]):
154+
# walk from a to b
155+
# open valve b
156+
time -= dist2[a,b]
157+
time -= 1
158+
result += time * f[b]
159+
return result#, time
160+
161+
# alternate part 1:
162+
# print(max(value(path, 30) for path in get_paths("AA", 30)))
163+
164+
best_value = 0
165+
ctr = 0
166+
for i,path1 in enumerate(get_paths("AA", 26, set())):
167+
if i % 100 == 0:
168+
print(i)
169+
p1v = value(path1, 26)
170+
for path2 in get_paths("AA", 26, exclude=set(path1)):
171+
ctr += 1
172+
p2v = value(path2, 26)
173+
best_value = max(best_value, p1v + p2v)
174+
print(ctr)
175+
print(best_value)

0 commit comments

Comments
 (0)