Advent of Code 14

In [56]:
def readfile(path):
    """Read and return file contents"""
    with open(path) as input_file: 
        return input_file.read().strip()
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:
        output.add(o)
        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:
            rdeps[dep].add(x)

    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.