#!/usr/bin/env python3 from collections import defaultdict ORE = "ORE" FUEL = "FUEL" class Reactor: def __init__(self, reactions, root=ORE): self.reactions = reactions self.root = root self.produced = defaultdict(int) self.leftover = defaultdict(int) def produce(self, el, n): if el == self.root: self.produced[el] += n return min = self.reactions[el][el] if type(min) == float: k = (n + min - 1) / min else: k = (n + min - 1) // min for el2, m in self.reactions[el].items(): if el2 == el: continue self.get(el2, k * m) p = k*min self.produced[el] += p self.leftover[el] += p - n def get(self, el, n): n -= self.leftover[el] self.leftover[el] = 0 if n <= 0: self.leftover[el] = -n return self.produce(el, n) def parse(line): el = line.split()[-1] line = line.replace(" => ", ', ') return el, dict((m.split()[1], int(m.split()[0])) for m in line.split(', ')) def ore_per_fuel(reactions, n=1): reactor = Reactor(reactions) reactor.get(FUEL, n) return reactor.produced[ORE] def ore_per_fuel_lim(reactions): float_recs = {} for el in reactions.keys(): min = float(reactions[el][el]) float_recs[el] = dict((el2, n/min) for el2, n in reactions[el].items()) return ore_per_fuel(float_recs) def search(f, target, best): step = best // 100 i = best while step > 0: while f(i) <= target: i += step i -= step step //= 10 return i def preproc(puzzle_input): return dict(map(parse, puzzle_input.split('\n'))) def partI(reactions): r = ore_per_fuel(reactions) return r def partII(reactions): ore = 10**12 best = int(ore // ore_per_fuel_lim(reactions)) return search(lambda n: ore_per_fuel(reactions, n), ore, best) from main import Tests tests = Tests() tests.add( """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""", partI=13312) tests.add( """2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG 17 NVRVD, 3 JNWZP => 8 VPVL 53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL 22 VJHF, 37 MNCFX => 5 FWMGM 139 ORE => 4 NVRVD 144 ORE => 7 JNWZP 5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC 5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV 145 ORE => 6 MNCFX 1 NVRVD => 8 CXFTF 1 VJHF, 6 MNCFX => 4 RFSQX 176 ORE => 6 VJHF""", partI=180697) tests.add( """171 ORE => 8 CNZTR 7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL 114 ORE => 4 BHXH 14 VRPVC => 6 BMBT 6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL 6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT 15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW 13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW 5 BMBT => 4 WPTQ 189 ORE => 9 KTJDG 1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP 12 VRPVC, 27 CNZTR => 2 XDBXC 15 KTJDG, 12 BHXH => 5 XCVML 3 BHXH, 2 VRPVC => 7 MZWV 121 ORE => 7 VRPVC 7 XCVML => 6 RJRHP 5 BHXH, 4 VRPVC => 5 LTCX""", partI=2210736)