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.

Add 2022/19

+116
+116
2022/day19.py
··· 1 + import fileinput 2 + from collections import deque 3 + 4 + from utils import mul, parse_nums 5 + 6 + 7 + # Parse input. 8 + BLUEPRINTS = [] 9 + 10 + for line in fileinput.input(): 11 + BLUEPRINTS.append(parse_nums(line)) 12 + 13 + 14 + def simulate(bp, max_time=24): 15 + id_num, ore_cost, clay_cost, ob_ore, ob_clay, geo_ore, geo_ob = bp 16 + 17 + # Pruning Heuristic #1: Cap Resources 18 + # 19 + # Since we can only build one robot per minute, at a certain point, 20 + # we will have so much of a resource that collecting any more will 21 + # make no effective difference to the end goal of mining geodes. 22 + # 23 + # In order to keep the search space smaller, cap the number of the 24 + # lower-level resources based on trial-and-error multipliers on the 25 + # resource costs of the various robots. 26 + MAX_ORE_MULTIPLIER = 1.5 27 + MAX_CLAY_MULTIPLER = 1.5 28 + MAX_OB_MULTIPLIER = 1 29 + 30 + max_ore = (ore_cost + clay_cost + ob_ore + geo_ore) * MAX_ORE_MULTIPLIER 31 + max_clay = ob_clay * MAX_CLAY_MULTIPLER 32 + max_ob = geo_ob * MAX_CLAY_MULTIPLER 33 + 34 + # minutes, ore_bots, clay_bots, ob_bots, geo_bots, ore, clay, ob, geo 35 + state = (1, 1, 0, 0, 0, 0, 0, 0, 0) 36 + horizon = deque([state]) 37 + seen = {} 38 + 39 + best = 0 40 + 41 + while horizon: 42 + state = horizon.popleft() 43 + minutes, ore_bots, clay_bots, ob_bots, geo_bots, ore, clay, ob, geo = state 44 + 45 + # Cap resources before checking/saving state. 46 + ore = min(ore, max_ore) 47 + clay = min(clay, max_clay) 48 + max_ob = min(ob, max_ob) 49 + 50 + key = (minutes, ore_bots, clay_bots, ob_bots, geo_bots) 51 + val = (ore, clay, ob, geo) 52 + if key in seen: 53 + if all(a <= b for a, b in zip(val, seen[key])): 54 + continue 55 + 56 + if minutes >= max_time + 1: 57 + if geo > best: 58 + best = geo 59 + continue 60 + 61 + seen[key] = val 62 + 63 + next_poss = [] 64 + 65 + # Pruning Heuristic #2: Limit Lower Bots 66 + # 67 + # Once we get to the "late-game" and are building geode robots, 68 + # building additional ore or clay (and even obsidian) bots becomes 69 + # a waste, as it doesn't help improve the ability to build geode 70 + # robots as quickly as possible. 71 + # 72 + # We define some heuristics based on when to stop building the 73 + # lower-level bots, based on how many geode robots we have. 74 + GEO_ORE_STOPPAGE = 1 75 + GEO_CLAY_STOPPAGE = 1 76 + GEO_OB_STOPPAGE = 7 77 + 78 + # Build a geode bot. 79 + if ore >= geo_ore and ob >= geo_ob: 80 + ns = minutes + 1, ore_bots, clay_bots, ob_bots, geo_bots + 1, ore - geo_ore, clay, ob - geo_ob, geo 81 + next_poss.append(ns) 82 + 83 + # Build an obsidian bot. 84 + if ore >= ob_ore and clay >= ob_clay and geo_bots < GEO_OB_STOPPAGE: 85 + ns = minutes + 1, ore_bots, clay_bots, ob_bots + 1, geo_bots, ore - ob_ore, clay - ob_clay, ob, geo 86 + next_poss.append(ns) 87 + 88 + # Build a clay bot. 89 + if ore >= clay_cost and geo_bots < GEO_CLAY_STOPPAGE: 90 + ns = minutes + 1, ore_bots, clay_bots + 1, ob_bots, geo_bots, ore - clay_cost, clay, ob, geo 91 + next_poss.append(ns) 92 + 93 + # Build an ore bot. 94 + if ore >= ore_cost and geo_bots < GEO_ORE_STOPPAGE: 95 + ns = minutes + 1, ore_bots + 1, clay_bots, ob_bots, geo_bots, ore - ore_cost, clay, ob, geo 96 + next_poss.append(ns) 97 + 98 + # Don't build anything; just accrue resources. 99 + if ore < max_ore: 100 + ns = minutes + 1, ore_bots, clay_bots, ob_bots, geo_bots, ore, clay, ob, geo 101 + next_poss.append(ns) 102 + 103 + # Actually acquire resources. 104 + for ns in next_poss: 105 + m, oreb, clayb, obb, geob, ore, clay, ob, geo = ns 106 + horizon.append((m, oreb, clayb, obb, geob, ore + ore_bots, clay + clay_bots, ob + ob_bots, geo + geo_bots)) 107 + 108 + return best 109 + 110 + 111 + quality_levels = [simulate(bp, max_time=24) * bp[0] for bp in BLUEPRINTS] 112 + print("Part 1:", (sum(quality_levels))) 113 + 114 + part_2 = [simulate(bp, max_time=32) for bp in BLUEPRINTS[:3]] 115 + part_2.sort(reverse=True) 116 + print("Part 2:", mul(part_2))