Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2019/day14/part2.rb
2021-12-04 21:20:57 -08:00

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