Advent of Code 2022 day 19
Branching is incredibly expensive. Theoretically, this problem wasn’t particularly challenging. In practice though, it was infuriating. Many different attempts to speed up the program have proved to be unfruitful. The answers were obtained from half-mangled programs which have been lost to the ether. Below are the remnants.
Part 1
from itertools import product
from functools import cached_property
class Blueprint:
def __init__(
self,
number,
ore_robot_ore,
clay_robot_ore,
obsidian_robot_ore,
obsidian_robot_clay,
geode_robot_ore,
geode_robot_obsidian,
):
self.number = number
self.ore_robot_ore = ore_robot_ore
self.clay_robot_ore = clay_robot_ore
self.obsidian_robot_ore = obsidian_robot_ore
self.obsidian_robot_clay = obsidian_robot_clay
self.geode_robot_ore = geode_robot_ore
self.geode_robot_obsidian = geode_robot_obsidian
def __hash__(self):
return self.number
def get_blueprints(line):
(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian
) = [int(x) for x in line.replace(":", "").split(" ") if x.isnumeric()]
return Blueprint(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian,
)
with open("19.txt") as f:
# with open("19testtest.txt") as f:
lines = f.read().splitlines()
max_geode = 0
history = {}
def tree(
blueprint,
time = 24,
ore = 0,
clay = 0,
obsidian = 0,
geode = 0,
ore_robot = 1,
clay_robot = 0,
obsidian_robot = 0,
geode_robot = 0,
add_clay_robot = True,
add_ore_robot = True,
):
global max_geode
global history
key = (
time,
ore,
clay,
obsidian,
geode,
ore_robot,
clay_robot,
geode_robot,
add_clay_robot and blueprint.clay_robot_ore <= ore,
add_ore_robot and blueprint.ore_robot_ore <= ore,
)
if key in history:
return history[key]
# print(
# "blueprint:",
# blueprint.number,
# "time:",
# time,
# "max_geode:",
# max_geode,
# "geode:",
# geode,
# "robots:",
# geode_robot,
# obsidian_robot,
# clay_robot,
# ore_robot,
# "ores:",
# ore,
# clay,
# obsidian,
# geode,
# )
if geode > max_geode:
max_geode = geode
if time == 0:
history[key] = geode
return geode
if (geode + (1.5*time*(geode_robot+1)) < max_geode):
history[key] = 0
return 0
new_geode_robot = geode_robot
new_obsidian_robot = obsidian_robot
new_clay_robot = clay_robot
new_ore_robot = ore_robot
if (
blueprint.geode_robot_ore <= ore and
blueprint.geode_robot_obsidian <= obsidian
):
ore -= blueprint.geode_robot_ore
obsidian -= blueprint.geode_robot_obsidian
new_geode_robot += 1
elif (
blueprint.obsidian_robot_ore <= ore and
blueprint.obsidian_robot_clay <= clay
):
ore -= blueprint.obsidian_robot_ore
clay -= blueprint.obsidian_robot_clay
new_obsidian_robot += 1
elif (
add_clay_robot and
blueprint.clay_robot_ore <= ore
):
ore -= blueprint.clay_robot_ore
new_clay_robot += 1
elif (
add_ore_robot and
blueprint.ore_robot_ore <= ore
):
ore -= blueprint.ore_robot_ore
new_ore_robot += 1
ans = max(
tree(
blueprint,
time = time-1,
ore = ore + ore_robot,
clay = clay + clay_robot,
obsidian = obsidian + obsidian_robot,
geode = geode + geode_robot,
ore_robot = new_ore_robot,
clay_robot = new_clay_robot,
obsidian_robot = new_obsidian_robot,
geode_robot = new_geode_robot,
add_clay_robot = new_add_clay_robot,
add_ore_robot = new_add_ore_robot,
)
for (
new_add_clay_robot,
new_add_ore_robot
) in product((True, False), repeat=2)
)
history[key] = ans
return ans
def get_geodes(blueprint):
global max_geode
global history
max_geode = 0
history = {}
ans = tree(blueprint)
print(blueprint.number, ans, max_geode)
return tree(blueprint)
blueprints = [get_blueprints(line) for line in lines]
print(
"answer",
sum([
get_geodes(blueprint) * blueprint.number
for blueprint in blueprints
])
)
Part 2 (incredibly slow)
from itertools import product
from random import randint
from functools import cached_property
class Blueprint:
def __init__(
self,
number,
ore_robot_ore,
clay_robot_ore,
obsidian_robot_ore,
obsidian_robot_clay,
geode_robot_ore,
geode_robot_obsidian,
):
self.number = number
self.ore_robot_ore = ore_robot_ore
self.clay_robot_ore = clay_robot_ore
self.obsidian_robot_ore = obsidian_robot_ore
self.obsidian_robot_clay = obsidian_robot_clay
self.geode_robot_ore = geode_robot_ore
self.geode_robot_obsidian = geode_robot_obsidian
def __hash__(self):
return self.number
def __str__(self):
return (
f"{self.number} "
f"ore ore: {self.ore_robot_ore} "
f"clay ore:{self.clay_robot_ore} "
f"obsidian ore: {self.obsidian_robot_ore} "
f"obsidian clay: {self.obsidian_robot_clay} "
f"geode ore: {self.geode_robot_ore} "
f"geode obsidian: {self.geode_robot_obsidian} "
)
def get_blueprints(line):
(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian
) = [int(x) for x in line.replace(":", "").split(" ") if x.isnumeric()]
return Blueprint(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian,
)
with open("19.txt") as f:
lines = f.read().splitlines()
def tree(
blueprint,
time = 24,
ore = 0,
clay = 0,
obsidian = 0,
geode = 0,
ore_robot = 1,
clay_robot = 0,
obsidian_robot = 0,
geode_robot = 0,
):
def can_build(t=None):
if t is None:
return [
x
for x in ["", "geode", "obsidian", "clay", "ore"]
if can_build(x)
]
elif t == "":
return True
elif t == "geode":
return (
blueprint.geode_robot_ore <= ore and
blueprint.geode_robot_obsidian <= obsidian
)
elif t == "obsidian":
return (
blueprint.obsidian_robot_ore <= ore and
blueprint.obsidian_robot_clay <= clay
)
elif t == "clay":
return blueprint.clay_robot_ore <= ore
elif t == "ore":
return blueprint.ore_robot_ore <= ore
def new_ore(build):
if build == "":
return ore + ore_robot
elif build == "ore":
return ore + ore_robot - blueprint.ore_robot_ore
elif build == "clay":
return ore + ore_robot - blueprint.clay_robot_ore
elif build == "obsidian":
return ore + ore_robot - blueprint.obsidian_robot_ore
elif build == "geode":
return ore + ore_robot - blueprint.geode_robot_ore
def new_clay(build):
if build == "obsidian":
return clay + clay_robot - blueprint.obsidian_robot_clay
return clay + clay_robot
def new_obsidian(build):
if build == "geode":
return obsidian + obsidian_robot - blueprint.geode_robot_obsidian
return obsidian + obsidian_robot
def new_geode(build):
return geode + geode_robot
def new_ore_robot(build):
if build == "ore":
return ore_robot + 1
return ore_robot
def new_clay_robot(build):
if build == "clay":
return clay_robot + 1
return clay_robot
def new_obsidian_robot(build):
if build == "obsidian":
return obsidian_robot + 1
return obsidian_robot
def new_geode_robot(build):
if build == "geode":
return geode_robot + 1
return geode_robot
def get_most_needed(t=None, weights=None):
values = {}
if t is None:
t = ["geode", "obsidian", "clay", "ore", ""]
if weights is None:
weights = [10**6, 20, 10, 6, 3, 2, 1, 30, 1]
for x in t:
if x == "geode":
values[x] = weights[0]
elif x == "obsidian":
values[x] = (
(
blueprint.geode_robot_obsidian -
obsidian_robot
)*weights[1]
)
elif x == "clay":
values[x] = (
(blueprint.geode_robot_obsidian - obsidian_robot)*weights[2] +
(blueprint.obsidian_robot_clay - clay_robot)*weights[3]
)
elif x == "ore":
values[x] = (
(blueprint.geode_robot_ore - ore_robot)*weights[4] +
(blueprint.obsidian_robot_ore - ore_robot)*weights[5] +
(blueprint.clay_robot_ore - clay_robot)*weights[6] +
(blueprint.ore_robot_ore - ore_robot)*weights[7]
)
elif x == "":
values[x] = weights[8]
return sorted(values, key=lambda x: values[x], reverse=True)
def should_build():
return get_most_needed(can_build())
key = (
time,
"ores",
ore,
clay,
obsidian,
geode,
"robots",
ore_robot,
clay_robot,
obsidian_robot,
geode_robot,
)
global max_geodes
if geode > max_geodes:
max_geodes = geode
print(max_geodes)
if time == 0:
print(key)
return geode
if geode + time*(geode_robot + time/2) < max_geodes:
return 0
return max(
tree(
blueprint,
time = time-1,
ore = new_ore(build),
clay = new_clay(build),
obsidian = new_obsidian(build),
geode = new_geode(build),
ore_robot = new_ore_robot(build),
clay_robot = new_clay_robot(build),
obsidian_robot = new_obsidian_robot(build),
geode_robot = new_geode_robot(build),
) for build in should_build()
)
# for build in can_build():
# if build == "geode":
# ore -= blueprint.geode_robot_ore
# obsidian -= blueprint.geode_robot_obsidian
# new_geode_robot += 1
# elif build == "obsidian":
# ore -= blueprint.obsidian_robot_ore
# clay -= blueprint.obsidian_robot_clay
# new_obsidian_robot += 1
# elif build == "clay":
# ore -= blueprint.clay_robot_ore
# new_clay_robot += 1
# elif build == "ore":
# ore -= blueprint.ore_robot_ore
# new_ore_robot += 1
# return tree(
# blueprint,
# time = time-1,
# ore = ore + ore_robot,
# clay = clay + clay_robot,
# obsidian = obsidian + obsidian_robot,
# geode = geode + geode_robot,
# ore_robot = new_ore_robot,
# clay_robot = new_clay_robot,
# obsidian_robot = new_obsidian_robot,
# geode_robot = new_geode_robot,
# )
max_geodes = 0
def get_geodes(blueprint, time=24):
global max_geodes
max_geodes = 0
print(blueprint)
ans = tree(blueprint, time=time)
print(blueprint.number, ans)
return ans
blueprints = [get_blueprints(line) for line in lines]
answer = 1
for blueprint in blueprints[:3]:
geodes = get_geodes(blueprint, time=32)
answer *= geodes
print(answer)
Part 2 (quick, but not necessarily accurate)
from itertools import product
from random import randint
from functools import cached_property
class Blueprint:
def __init__(
self,
number,
ore_robot_ore,
clay_robot_ore,
obsidian_robot_ore,
obsidian_robot_clay,
geode_robot_ore,
geode_robot_obsidian,
):
self.number = number
self.ore_robot_ore = ore_robot_ore
self.clay_robot_ore = clay_robot_ore
self.obsidian_robot_ore = obsidian_robot_ore
self.obsidian_robot_clay = obsidian_robot_clay
self.geode_robot_ore = geode_robot_ore
self.geode_robot_obsidian = geode_robot_obsidian
def __hash__(self):
return self.number
def __str__(self):
return (
f"{self.number} "
f"ore ore: {self.ore_robot_ore} "
f"clay ore:{self.clay_robot_ore} "
f"obsidian ore: {self.obsidian_robot_ore} "
f"obsidian clay: {self.obsidian_robot_clay} "
f"geode ore: {self.geode_robot_ore} "
f"geode obsidian: {self.geode_robot_obsidian} "
)
def get_blueprints(line):
(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian
) = [int(x) for x in line.replace(":", "").split(" ") if x.isnumeric()]
return Blueprint(
number,
ore_robot,
clay_robot,
obsidian_ore,
obsidian_clay,
geode_ore,
geode_obsidian,
)
with open("19.txt") as f:
lines = f.read().splitlines()
def generate_weights(n=10000):
for _ in range(n):
yield [
10**6,
randint(1, 25),
randint(1, 25),
randint(1, 25),
randint(1, 25),
randint(1, 25),
randint(1, 25),
randint(1, 25),
randint(1, 25),
]
# for a, b, c, d, e, f, g in product(
# (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20),
# repeat=7
# ):
# yield [10**9, a, b, c, d, e, f, g]
def tree(
blueprint,
time = 24,
ore = 0,
clay = 0,
obsidian = 0,
geode = 0,
ore_robot = 1,
clay_robot = 0,
obsidian_robot = 0,
geode_robot = 0,
weights = None,
):
def can_build(t=None):
if t is None:
return [
x for x in ["geode", "obsidian", "clay", "ore"] if can_build(x)
]
elif t == "geode":
return (
blueprint.geode_robot_ore <= ore and
blueprint.geode_robot_obsidian <= obsidian
)
elif t == "obsidian":
return (
blueprint.obsidian_robot_ore <= ore and
blueprint.obsidian_robot_clay <= clay
)
elif t == "clay":
return blueprint.clay_robot_ore <= ore
elif t == "ore":
return blueprint.ore_robot_ore <= ore
# def time_to_build(t):
# if t == "obsidian":
# return max(
# 0,
# (blueprint.obsidian_robot_ore - ore)/ore_robot,
# (blueprint.obsidian_robot_clay - clay)/clay_robot
# )
# elif t == "clay":
# return max(
# 0,
# int((blueprint.clay_robot_ore - ore)/ore_robot)
# )
# elif t == "ore":
# return max(
# 0,
# int((blueprint.ore_robot_ore - ore)/ore_robot)
# )
def get_most_needed(t=None, weights=None):
values = {}
if t is None:
t = ["geode", "obsidian", "clay", "ore"]
if weights is None:
weights = [10**6, 10, 5, 2, 5, 2, 1, 30]
for x in t:
if x == "geode":
values[x] = weights[0]
elif x == "obsidian":
values[x] = (
(
blueprint.geode_robot_obsidian -
obsidian_robot
)*weights[1]
)
elif x == "clay":
values[x] = (
(blueprint.geode_robot_obsidian - obsidian_robot)*weights[2] +
(blueprint.obsidian_robot_clay - clay_robot)*weights[3]
)
elif x == "ore":
values[x] = (
(blueprint.geode_robot_ore - ore_robot)*weights[4] +
(blueprint.obsidian_robot_ore - ore_robot)*weights[5] +
(blueprint.clay_robot_ore - clay_robot)*weights[6] +
(blueprint.ore_robot_ore - ore_robot)*weights[7]
)
if len(values):
return sorted(values, key=lambda x: values[x])[-1]
return ""
def should_build(weights):
return get_most_needed(can_build(), weights=weights)
# def robot_worth_it(t):
# if t == "geode":
# return can_build(t)
# elif t == "obsidian":
# return (
# can_build(t) and
# not (
# (
# obsidian + obsidian_robot >=
# blueprint.geode_robot_obsidian
# ) and
# blueprint.obsidian_robot_ore < blueprint.geode_robot_ore
# )
# )
# elif t == "clay":
# return (
# can_build(t) and
# not (
# (
# obsidian + obsidian_robot >=
# blueprint.geode_robot_obsidian
# ) and
# blueprint.clay_robot_ore < blueprint.geode_robot_ore
# ) and
# not (
# ore <= blueprint.obsidian_robot_ore and
# blueprint.clay_robot_ore < blueprint.obsidian_robot_ore
# )
# )
# elif t == "ore":
# return (
# can_build(t) and
# blueprint.ore_robot_ore <= ore and
# not (
# ore < blueprint.clay_robot_ore and
# blueprint.ore_robot_ore < blueprint.clay_robot_ore
# )
# )
key = (
time,
"ores",
ore,
clay,
obsidian,
geode,
"robots",
ore_robot,
clay_robot,
obsidian_robot,
geode_robot,
)
if time == 0:
# print(key)
return geode
new_geode_robot = geode_robot
new_obsidian_robot = obsidian_robot
new_clay_robot = clay_robot
new_ore_robot = ore_robot
build = should_build(weights=weights)
if build == "geode":
ore -= blueprint.geode_robot_ore
obsidian -= blueprint.geode_robot_obsidian
new_geode_robot += 1
elif build == "obsidian":
ore -= blueprint.obsidian_robot_ore
clay -= blueprint.obsidian_robot_clay
new_obsidian_robot += 1
elif build == "clay":
ore -= blueprint.clay_robot_ore
new_clay_robot += 1
elif build == "ore":
ore -= blueprint.ore_robot_ore
new_ore_robot += 1
return tree(
blueprint,
time = time-1,
ore = ore + ore_robot,
clay = clay + clay_robot,
obsidian = obsidian + obsidian_robot,
geode = geode + geode_robot,
ore_robot = new_ore_robot,
clay_robot = new_clay_robot,
obsidian_robot = new_obsidian_robot,
geode_robot = new_geode_robot,
weights = weights
)
def get_geodes(blueprint, time=24):
print(blueprint)
ans = max(
tree(blueprint, time=time, weights=weights)
for weights in generate_weights()
)
print(blueprint.number, ans)
return ans
blueprints = [get_blueprints(line) for line in lines]
answer = 1
for blueprint in blueprints[:3]:
geodes = get_geodes(blueprint, time=32)
answer *= geodes
print(answer)