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

Switching from using frozensets to bitstrings to track what nodes
have been visited in the search state speeds things up by ~66%.

Before (sequential/parallel):
180.68s user 0.22s system 100% cpu 3:00.90 total
885.54s user 20.17s system 1545% cpu 58.601 total

After (sequential/parallel):
63.70s user 0.07s system 99% cpu 1:03.79 total
312.33s user 6.87s system 1524% cpu 20.942 total

+35 -12
+35 -12
2022/day16.py
··· 21 21 22 22 FLOW[valve] = rate 23 23 24 - # Construct compressed graph 24 + 25 + # Create compressed graph representations that only contain 26 + # information between valves that have a non-zero flow, where 27 + # rather than a unweighted graph of implicit distance 1, the 28 + # graph contains edge weights of the distance to get from 29 + # valve X to Y via the shortest path in the global graph. 25 30 def bfs(start, end): 26 31 seen = set() 27 32 horizon = deque([(start, 0)]) ··· 38 43 for n in GRAPH[curr]: 39 44 horizon.append((n, dist + 1)) 40 45 41 - 42 - # Solve part 1. 43 46 START = "AA" 44 47 START_TO_REAL = {} 45 - REAL = [v for v in GRAPH if FLOW[v] > 0] 48 + real = [v for v in GRAPH if FLOW[v] > 0] 49 + REAL = {v: i for i, v in enumerate(real)} 46 50 COMPRESSED = defaultdict(set) 47 51 48 52 for valve in REAL: ··· 54 58 COMPRESSED[b].add((a, dist)) 55 59 56 60 61 + # Define helper functions to operate on bitstring representations 62 + # 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 + def is_node_set_in_bitstring(bitstring, node): 71 + return bool(bitstring & (1 << REAL[node])) 72 + 73 + def set_node_in_bitstring(bitstring, node): 74 + return bitstring | (1 << REAL[node]) 75 + 76 + def is_bitstring_complete(bitstring): 77 + return bitstring + 1 == (1 << len(REAL)) 78 + 79 + 57 80 @memoize 58 81 def search(nodes, max_time): 59 82 best = 0 ··· 71 94 for node in nodes: 72 95 start_time = START_TO_REAL[node] + 1 73 96 start_pressure = (max_time - start_time) * FLOW[node] 74 - state = (start_time, node, start_pressure, frozenset([node])) 97 + state = (start_time, node, start_pressure, bitstring_for_subgraph([node])) 75 98 horizon.append(state) 76 99 77 100 while horizon: ··· 85 108 86 109 if time >= max_time: 87 110 continue 88 - elif len(opened) >= len(nodes): 111 + elif is_bitstring_complete(opened): 89 112 continue 90 113 91 114 if pressure > best: ··· 93 116 94 117 # move 95 118 for n, dist in compressed[curr]: 96 - if n not in opened: 97 - new_time = time + dist + 1 119 + if not is_node_set_in_bitstring(opened, n): 120 + new_time = time + dist + 1 # time to move + 1 minute to open 98 121 extra_pressure = (max_time - new_time) * FLOW[n] 99 - new_opened = frozenset([n]) | opened 122 + new_opened = set_node_in_bitstring(opened, n) 100 123 new_state = (new_time, n, pressure + extra_pressure, new_opened) 101 124 102 125 horizon.append(new_state) 103 126 104 127 return best 105 128 106 - print("Part 1:", search(COMPRESSED, 30)) 107 129 130 + # Solve part 1. 131 + print("Part 1:", search(REAL, 30)) 108 132 109 133 # Solve part 2. 110 134 def dual_search(partition): 111 135 a, b = partition 112 - return search(frozenset(a), 26) + search(frozenset(b), 26) 113 - 136 + return search(a, 26) + search(b, 26) 114 137 115 138 part_2 = 0 116 139 partitions = list(set_partitions(REAL, 2))