My Advent of Code solutions in Python. kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update 2022/16

I wrote this last year and don't remember if it works, but I found it
uncommitted in my Git repository. ¯\_(ツ)_/¯

+21 -21
+21 -21
2022/day16.py
··· 4 4 from itertools import permutations 5 5 from concurrent.futures import ProcessPoolExecutor 6 6 7 - from utils import parse_line, memoize 8 - 9 - from more_itertools import set_partitions 7 + from utils import parse_line 10 8 11 9 12 10 GRAPH = defaultdict(set) ··· 60 58 61 59 # Define helper functions to operate on bitstring representations 62 60 # of which valves have been visited, rather than using sets. 63 - def bitstring_for_subgraph(nodes): 64 - bitstring = 0b0 65 - for n in nodes: 66 - bitstring |= (1 << REAL[n]) 67 - 68 - return bitstring 69 - 70 61 def is_node_set_in_bitstring(bitstring, node): 71 62 return bool(bitstring & (1 << REAL[node])) 72 63 ··· 77 68 return bitstring + 1 == (1 << len(REAL)) 78 69 79 70 80 - @memoize 81 - def search(nodes, max_time): 71 + def search(init_opened, max_time): 82 72 best = 0 83 73 seen = {} 84 74 horizon = deque() 85 75 86 76 # Recompute compressed graph based on limited nodes available. 87 77 compressed = { 88 - a: [(n, d) for n, d in b if n in nodes] 89 - for a, b in COMPRESSED.items() if a in nodes 78 + a: [(n, d) for n, d in b if not is_node_set_in_bitstring(init_opened, n)] 79 + for a, b in COMPRESSED.items() if not is_node_set_in_bitstring(init_opened, a) 90 80 } 91 81 92 82 # Seed start positions with time and pressure based on the time it takes 93 83 # to get from AA to that valve, + 1 minute to open the valve. 94 - for node in nodes: 84 + for node in REAL: 85 + if is_node_set_in_bitstring(init_opened, node): 86 + continue 87 + 95 88 start_time = START_TO_REAL[node] + 1 96 89 start_pressure = (max_time - start_time) * FLOW[node] 97 - state = (start_time, node, start_pressure, bitstring_for_subgraph([node])) 90 + start_opened = set_node_in_bitstring(init_opened, node) 91 + state = (start_time, node, start_pressure, start_opened) 98 92 horizon.append(state) 99 93 100 94 while horizon: ··· 115 109 best = pressure 116 110 117 111 # move 118 - for n, dist in compressed[curr]: 112 + for n, dist in COMPRESSED[curr]: 119 113 if not is_node_set_in_bitstring(opened, n): 120 114 new_time = time + dist + 1 # time to move + 1 minute to open 121 115 extra_pressure = (max_time - new_time) * FLOW[n] ··· 128 122 129 123 130 124 # Solve part 1. 131 - print("Part 1:", search(REAL, 30)) 125 + print("Part 1:", search(0b0, 30)) 132 126 133 127 # Solve part 2. 134 128 def dual_search(partition): ··· 136 130 return search(a, 26) + search(b, 26) 137 131 138 132 part_2 = 0 139 - partitions = list(set_partitions(REAL, 2)) 133 + 134 + # Given the bitstring representation of opened nodes, the set of all possibilities is 135 + # just all pairs of numbers that sum to 2^(len(REAL)) - 1. We iterate over the range 136 + # of half this amount to ensure we don't double-up on pairs, thus removing the need 137 + # for any sort of memoization (we compute precisely the results we need). 138 + target = (1 << len(REAL)) - 1 139 + partitions = [(n, target - n) for n in range(1 << (len(REAL) - 1))] 140 140 141 141 # This is not the optimal solution yet. If it were, I wouldn't be parallelizing. 142 - parallel = True 142 + parallel = False 143 143 144 144 if parallel: 145 145 with ProcessPoolExecutor() as pool: ··· 156 156 except ImportError: 157 157 loading = lambda x, total: x 158 158 159 - for p in loading(set_partitions(REAL, 2), total=len(partitions)): 159 + for p in loading(partitions, total=len(partitions)): 160 160 part_2 = max(part_2, dual_search(p)) 161 161 162 162 print("Part 2:", part_2)