130 lines
2.5 KiB
Ruby
130 lines
2.5 KiB
Ruby
# 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
|