Advent of Code 14¶

In [56]:
def readfile(path):
"""Read and return file contents"""
with open(path) as input_file:

In [57]:
testinputs = ["""10 ORE => 10 A
1 ORE => 1 B
7 A, 1 B => 1 C
7 A, 1 C => 1 D
7 A, 1 D => 1 E
7 A, 1 E => 1 FUEL""",
"""9 ORE => 2 A
8 ORE => 3 B
7 ORE => 5 C
3 A, 4 B => 1 AB
5 B, 7 C => 1 BC
4 C, 1 A => 1 CA
2 AB, 3 BC, 4 CA => 1 FUEL""",
"""157 ORE => 5 NZVS
165 ORE => 6 DCFZ
44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL
12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ
179 ORE => 7 PSHF
177 ORE => 5 HKGWZ
7 DCFZ, 7 PSHF => 2 XJWVT
165 ORE => 2 GPVTF
3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT"""]

In [58]:
def parse_val(val):
pieces = val.split(" ")
return (pieces[1], int(pieces[0]))

def parse_line(ln):
ins, out = ln.split(" => ")
ins = map(parse_val, ins.split(", "))
out = parse_val(out)
return out, list(ins)

def process(inp):
reactions = list(map(parse_line, inp.split("\n")))
data = {}
for out, ins in reactions:
data[out[0]] = [out[1], ins]
return data

def deps(data, x, output=None):
if output is None:
output = set()

if x not in data:
return output

ins = data[x][1]
for o, val in ins:
deps(data, o, output)

return output

def postprocess(data):
for out in data:
data[out].append(deps(data, out))
return data

postprocess(process(testinputs[0]))

Out[58]:
{'A': [10, [('ORE', 10)], {'ORE'}],
'B': [1, [('ORE', 1)], {'ORE'}],
'C': [1, [('A', 7), ('B', 1)], {'A', 'B', 'ORE'}],
'D': [1, [('A', 7), ('C', 1)], {'A', 'B', 'C', 'ORE'}],
'E': [1, [('A', 7), ('D', 1)], {'A', 'B', 'C', 'D', 'ORE'}],
'FUEL': [1, [('A', 7), ('E', 1)], {'A', 'B', 'C', 'D', 'E', 'ORE'}]}
In [59]:
from collections import defaultdict
import math

def get_ore(data, req={'FUEL': 1}):
rdeps = defaultdict(set)
for x, (count, total, deps) in data.items():
for dep in deps:

spares = defaultdict(lambda: 0)
while True:
# print(req, spares)
next_req = defaultdict(lambda: 0)
active_elems = req.keys()
for elem, amt in req.items():
if elem in data and not (active_elems & rdeps.get(elem, set())):
min_amt = data[elem][0]
multiplier = math.ceil(amt / min_amt)

if spares[elem] >= amt:
spares[elem] -= amt
else:
req_amt = amt - spares[elem]
spares[elem] += multiplier * min_amt - amt

for inp, amt in data[elem][1]:
next_req[inp] += amt * multiplier
else:
next_req[elem] += amt

req = next_req
if len(next_req) == 1:
break
return req

get_ore(postprocess(process(testinputs[0])))

def f(x):
return get_ore(postprocess(process(x)))

In [60]:
get_ore(postprocess(process(testinputs[1])))

Out[60]:
defaultdict(<function __main__.get_ore.<locals>.<lambda>()>, {'ORE': 165})
In [61]:
f(testinputs[2])

Out[61]:
defaultdict(<function __main__.get_ore.<locals>.<lambda>()>, {'ORE': 13312})
In [62]:
f(readfile("AoC/input14.txt"))

Out[62]:
defaultdict(<function __main__.get_ore.<locals>.<lambda>()>, {'ORE': 158482})
In [63]:
def max_fuel(inp, available_ore=1000000000000):
fuel = 1
used_ore = 0

while used_ore <= available_ore:
data = postprocess(process(inp))
used_ore = get_ore(data, req={'FUEL': fuel})["ORE"]
if used_ore <= available_ore:
fuel *= 2

start = fuel // 2
end = fuel

index = 0
while index <= 1000 and start != end - 1:
index += 1

mid = (start + end) // 2
used_ore = get_ore(data, req={'FUEL': mid})["ORE"]
if used_ore < available_ore:
start = mid
else:
end = mid

print(get_ore(data, req={'FUEL': start})["ORE"])
return start
max_fuel(testinputs[2])

999999999076

Out[63]:
82892753
In [64]:
max_fuel(readfile("AoC/input14.txt"))

999999897002

Out[64]:
7993831

I was a bit too tired and ended up falling asleep before midnight today, which means no meaningful ranks. I took the opportunity to switch out how I edit notebooks this time around, and set up EIN: Emacs-IPython-Notebooks and this seems like a magically better experience than Jupyter Lab. I can easily navigate through the full notebook, search works exactly like I would expect it to. There are of course some mild visual rough edges, but this was a really good experience otherwise.