diff --git a/2019/day14/part2.rb b/2019/day14/part2.rb new file mode 100644 index 0000000..c53b6cb --- /dev/null +++ b/2019/day14/part2.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +Chemical = Struct.new(:amount, :name) do + def pretty_print(pp) + pp.text to_s + end + + def to_s + "#{amount} #{name}" + end +end + +Formula = Struct.new(:product, :reactants) do + def produces?(name) + product.name == name + end + + def pretty_print(pp) + pp.text to_s + end + + def to_s + "#{product} <= #{reactants.join(', ')}" + end +end + +def parse_chemical(text) + parts = text.split(' ') + amount = parts[0].to_i + name = parts[1].strip + Chemical.new(amount, name) +end + +def parse(input) + input.lines.map do |line| + left, right = line.split('=>') + + product = parse_chemical(right) + reactants = left.split(',').map { |text| parse_chemical(text) } + Formula.new(product, reactants) + end +end + +def ore_needed(formulas, fuel) + have = Hash.new { |hash, key| hash[key] = 0 } + need = Hash.new { |hash, key| hash[key] = 0 } + + have = Hash.new { |hash, key| hash[key] = 0 } + need = Hash.new { |hash, key| hash[key] = 0 } + need['FUEL'] = fuel + ore = 0 + + until need.empty? + product, amount = need.shift + next if amount.zero? + + formula = formulas.find { |f| f.produces? product } + + needed = (amount - have[product]) + + runs = (needed.fdiv formula.product.amount).ceil + + formula.reactants.each do |r| + wanted = runs * r.amount + already_have = have[r.name] + can_use = [already_have, wanted].min + more = wanted - can_use + + have[r.name] -= can_use + if r.name == 'ORE' + ore += more + else + need[r.name] += more + end + end + + yielded = runs * formula.product.amount + extra = yielded - needed + have[product] += extra + end + + ore +end + +class OreCalculator + def initialize(formulas) + @formulas = formulas + @memo = {} + end + + def calc(fuel) + return @memo[fuel] if @memo.key?(fuel) + + ore = ore_needed(@formulas, fuel) + @memo[fuel] = ore + ore + end +end + +def binary_search(f, lo, hi, target) + until (f.call(hi) - f.call(lo)).zero? || hi - lo < 2 + mid = (hi + lo) / 2 + case f.call(mid) <=> target + when -1 + lo = mid + when 0 + break + when +1 + hi = mid + end + end + + lo +end + +input = File.read(File.join(__dir__, 'input')) +formulas = parse(input) + +available_ore = 1_000_000_000_000 + +upper_threshold = 0 +upper_fuel = 1 +until upper_threshold > available_ore + upper_threshold = ore_needed(formulas, upper_fuel) + upper_fuel *= 2 +end + +oc = OreCalculator.new(formulas) +fuel_made = binary_search(oc.method(:calc), 0, upper_fuel, available_ore) +puts fuel_made