Solve 2018 day 21, 2020 day 12 and improve 2022 day 19

This commit is contained in:
2024-09-13 20:22:22 -04:00
parent 9cd17279d9
commit 4a72eeb348
4 changed files with 192 additions and 111 deletions

View File

@@ -1,117 +1,66 @@
from lib import *
import re
from lib import get_data, str_to_ints
from math import ceil
EXAMPLE = """
Blueprint 1:
Each ore robot costs 4 ore.
Each clay robot costs 2 ore.
Each obsidian robot costs 3 ore and 14 clay.
Each geode robot costs 2 ore and 7 obsidian.
data = get_data(__file__)
Blueprint 2:
Each ore robot costs 2 ore.
Each clay robot costs 3 ore.
Each obsidian robot costs 3 ore and 8 clay.
Each geode robot costs 3 ore and 12 obsidian.
"""
p1 = 0
p2 = 1
def solve(input: Input, second=False):
if not second:
res = 0
else:
res = 1
for line in data.splitlines():
id = str_to_ints(line)[0]
bps = []
for line in input.text.replace(".", ".\n").replace(":", ":\n").splitlines():
if "Blueprint" in line:
bps.append([])
elif "ore robot" in line:
cost = str_to_ints(line)
cost += [0, 0]
bps[-1].append(cost)
elif "clay robot" in line:
cost = str_to_ints(line)
cost += [0, 0]
bps[-1].append(cost)
elif "obsidian robot" in line:
cost = str_to_ints(line)
cost += [0,]
bps[-1].append(cost)
elif "geode robot" in line:
cost = str_to_ints(line)
cost.insert(1, 0)
bps[-1].append(cost)
maxmin = [0, 0, 0]
for part in line.split(". "):
bp = []
for amount, mineral in re.findall(r"(\d+) (\w+)", part):
amount = int(amount)
mi = ["ore", "clay", "obsidian", "geode"].index(mineral)
bp.append((amount, mi))
maxmin[mi] = max(maxmin[mi], amount)
bps.append(bp)
if second:
bps = bps[:3]
time = 32
else:
time = 24
def dfs(time, mins, bots, cache):
if time == 0:
return mins[3]
end_states = []
for i, bp in enumerate(bps):
# ((ore bots, clay bots, obs bots, geo bots), (ore, clay, obs, geo))
start = ((1, 0, 0, 0), (0, 0, 0, 0))
states = [start]
seen = set(states)
key = (time, *mins, *bots)
if key in cache:
return cache[key]
for _ in range(time):
new_states = []
while states:
bots, ress = states.pop()
maxv = mins[3] + time * bots[3]
for bi, recipe in enumerate(bps):
if bi != 3 and bots[bi] >= maxmin[bi]:
continue
add_ress = [0, 0, 0, 0]
for boti, count in enumerate(bots):
add_ress[boti] += count
wait = 0
for ra, ri in recipe:
if bots[ri] == 0:
break
wait = max(ceil((ra - mins[ri]) / bots[ri]), wait)
else:
remtime = time - wait - 1
if remtime <= 0:
continue
all_built = True
for boti, cost in enumerate(bp):
if ress[0] >= cost[0] and ress[1] >= cost[1] and ress[2] >= cost[2]:
new_ress = list(ress)
new_ress[0] -= cost[0]
new_ress[1] -= cost[1]
new_ress[2] -= cost[2]
new_ress = tuple(map(sum, zip(new_ress, add_ress)))
new_bots = list(bots)
new_bots[boti] += 1
new_state = (tuple(new_bots), new_ress)
if not new_state in seen:
new_states.append(new_state)
seen.add(new_state)
else:
all_built = False
bots_ = list(bots)
mins_ = [m + b * (wait + 1) for m, b in zip(mins, bots)]
for ra, ri in recipe:
mins_[ri] -= ra
bots_[bi] += 1
# XXX: our search space is too large here it is possible to
# optimze by not storing reduntant paths (paths where we acrue
# more of a resource than we need), but I don't know how to
# make it more efficient right now.
if not all_built:
new_ress = tuple(map(sum, zip(ress, add_ress)))
new_state = (bots, new_ress)
if not new_state in seen:
new_states.append(new_state)
seen.add(new_state)
for i in range(3):
mins_[i] = min(mins_[i], maxmin[i] * remtime)
v = dfs(remtime, tuple(mins_), tuple(bots_), cache)
maxv = max(v, maxv)
# prune to keep search space "reasonable"
states = sorted(new_states, key=lambda s: list(reversed(s[0])), reverse=True)[:100000]
key = (time, *mins, *bots)
cache[key] = maxv
return maxv
if not second:
r = max(states, key=lambda s: s[1][3])
q = (i + 1) * r[1][3]
# print(i + 1, r, q)
res += q
else:
r = max(states, key=lambda r: r[1][3])
res *= r[1][3]
return res
p1 += id * dfs(24, (0, 0, 0, 0), (1, 0, 0, 0), {})
if id < 4:
p2 *= dfs(32, (0, 0, 0, 0), (1, 0, 0, 0), {})
def main():
DAY_INPUT = "d19.txt"
e1 = solve(Input(EXAMPLE))
print("Example 1:", e1)
assert e1 == 33
print("Solution 1:", solve(Input(DAY_INPUT)))
print("Example 2:", solve(Input(EXAMPLE), True))
print("Solution 2:", solve(Input(DAY_INPUT), True))
return
if __name__ == "__main__":
main()
print(p1)
print(p2)