aoc2022/day16.py

215 lines
6.5 KiB
Python

import heapq
import itertools
import re
from tools.aoc import AOCDay
from typing import Any
class Valve:
def __init__(self, name: str, flowrate: int) -> None:
self.__name = name
self.__flowrate = flowrate
self.neighbours = {}
@property
def flowrate(self) -> int:
return self.__flowrate
@property
def name(self) -> str:
return self.__name
def shortest_path_to(self, other: 'Valve'):
if other in self.neighbours:
return self.neighbours[other]
current = self
v = set()
v.add(current)
q = [(v, k) for k, v in self.neighbours.items()]
heapq.heapify(q)
while q:
dist, current = heapq.heappop(q)
if current == other:
return dist
if current in v:
continue
v.add(current)
for n, d in current.neighbours.items():
heapq.heappush(q, (dist + d, n))
def __lt__(self, other: 'Valve') -> bool:
return self.__name < other.__name
def __repr__(self) -> str:
return "Valve(%s;%s)" % (self.__name, self.__flowrate)
def __str__(self) -> str:
return self.__repr__()
def get_most_pressure_release_solo(root: Valve, remaining_minutes: int = 30, visited: set = None) -> int:
if visited is None:
visited = set()
visited.add(root)
my_flowrate = 0
if root.flowrate > 0:
remaining_minutes -= 1
my_flowrate = root.flowrate * remaining_minutes
max_flowrate = 0
for n, d in root.neighbours.items():
if n in visited:
continue
if remaining_minutes <= d + 1:
continue
n_flowrate = get_most_pressure_release_solo(n, remaining_minutes - d, visited.copy())
if n_flowrate > max_flowrate:
max_flowrate = n_flowrate
return max_flowrate + my_flowrate
def get_mode_pressure_release_double(root_me: Valve, root_ele: Valve, r_min_me: int = 26, r_min_ele: int = 26, visited: set = None) -> int:
dp_key = root_me.name + root_ele.name + "%02d" % r_min_me + "%02d" % r_min_ele + "".join(v.name for v in sorted(visited))
dp_key2 = root_ele.name + root_me.name + "%02d" % r_min_me + "%02d" % r_min_ele + "".join(v.name for v in sorted(visited))
if dp_key in DP:
return DP[dp_key]
my_flowrate = 0
if r_min_me > 1 and root_me not in visited:
visited.add(root_me)
r_min_me -= 1
my_flowrate += root_me.flowrate * r_min_me
if r_min_ele > 1 and root_ele not in visited:
visited.add(root_ele)
r_min_ele -= 1
my_flowrate += root_ele.flowrate * r_min_ele
if not my_flowrate and root_me.name != 'AA':
return 0
max_flowrate = 0
rem_list = [x for x in root_me.neighbours.keys() if x not in visited]
if len(rem_list) == 1:
if root_me.neighbours[rem_list[0]] < root_ele.neighbours[rem_list[0]]:
max_flowrate = get_mode_pressure_release_double(rem_list[0], root_ele, r_min_me - root_me.neighbours[rem_list[0]], r_min_ele, visited.copy())
else:
max_flowrate = get_mode_pressure_release_double(root_me, rem_list[0], r_min_me, r_min_ele - root_ele.neighbours[rem_list[0]], visited.copy())
else:
max_prod = len(rem_list) ** 2
cur_prod = 0
for v1, v2 in itertools.product(rem_list, repeat=2):
if len(visited) == 1:
cur_prod += 1
print("Iter (%d/%d)" % (cur_prod, max_prod))
if v1 == v2:
continue
me, ele = v1, v2
if r_min_me <= root_me.neighbours[me] + 1:
me = root_me
if r_min_ele <= root_ele.neighbours[ele] + 1:
ele = root_ele
if me == root_me and ele == root_ele:
continue
n_flowrate = get_mode_pressure_release_double(
me, ele,
r_min_me - (root_me.neighbours[me] if me in root_me.neighbours else 1),
r_min_ele - (root_ele.neighbours[ele] if ele in root_ele.neighbours else 1),
visited.copy()
)
if n_flowrate > max_flowrate:
max_flowrate = n_flowrate
if n_flowrate == 1671:
break
DP[dp_key] = my_flowrate + max_flowrate
DP[dp_key2] = my_flowrate + max_flowrate
return my_flowrate + max_flowrate
DP = {}
class Day(AOCDay):
input_regexp = re.compile(r'Valve ([A-Z][A-Z]) has flow rate=([0-9]+); tunnels? leads? to valves? (.*)')
inputs = [
[
(1651, "input16_test"),
(1947, "input16_dennis"),
(1850, "input16"),
],
[
(1707, "input16_test"),
(2556, "input16_dennis"),
(2306, "input16"),
]
]
def parse_input(self) -> Valve:
n_cache = {}
valves = {}
for line in self.getInput():
name, flowrate, neighbours = self.input_regexp.match(line).groups()
valves[name] = Valve(name, int(flowrate))
n_cache[name] = neighbours.split(", ")
for valve, neighbours in n_cache.items():
for n in neighbours:
valves[valve].neighbours[valves[n]] = 1
for valve in valves.values():
if valve.flowrate != 0:
continue
for n_set in valve.neighbours.keys():
del n_set.neighbours[valve]
for n_get in valve.neighbours.keys():
if n_get == n_set:
continue
if n_get in n_set.neighbours:
continue
n_set.neighbours[n_get] = valve.neighbours[n_set] + valve.neighbours[n_get]
for v_set in valves.values():
for v_get in valves.values():
if v_set == v_get or v_get in v_set.neighbours or v_get.flowrate == 0:
continue
v_set.neighbours[v_get] = v_set.shortest_path_to(v_get)
for n in list(valves['AA'].neighbours.keys()):
if n.flowrate == 0:
del valves['AA'].neighbours[n]
return valves['AA']
def part1(self) -> Any:
return get_most_pressure_release_solo(self.parse_input())
def part2(self) -> Any:
global DP
DP = {}
root_valve = self.parse_input()
return get_mode_pressure_release_double(root_valve, root_valve, visited={root_valve})
if __name__ == '__main__':
day = Day(2022, 16)
day.run(verbose=True)