Files
2019/days/14.go
2022-06-12 11:21:40 -05:00

128 lines
2.9 KiB
Go

package days
import (
"fmt"
"math"
"strconv"
"strings"
u "parnic.com/aoc2019/utilities"
)
type reaction struct {
inputs map[string]int
output u.Pair[string, int]
}
type Day14 struct {
reactions []reaction
leftovers map[string]int
}
func (d *Day14) Parse() {
d.leftovers = make(map[string]int)
lines := u.GetStringLines("14p")
d.reactions = make([]reaction, len(lines))
for i, line := range lines {
sides := strings.Split(line, " => ")
inputs := strings.Split(sides[0], ", ")
output := sides[1]
outPair := strings.Split(output, " ")
outAmt, _ := strconv.Atoi(outPair[0])
d.reactions[i].output = u.Pair[string, int]{First: outPair[1], Second: outAmt}
d.reactions[i].inputs = make(map[string]int)
for _, input := range inputs {
pair := strings.Split(input, " ")
d.reactions[i].inputs[pair[1]], _ = strconv.Atoi(pair[0])
}
}
}
func (d Day14) getReactionProducing(chem string) *reaction {
for _, reaction := range d.reactions {
if reaction.output.First == chem {
return &reaction
}
}
return nil
}
func (d Day14) Num() int {
return 14
}
func (d *Day14) oreRequiredStock(qty int64) int64 {
oreRequired := int64(0)
needs := map[string]int64{
"FUEL": qty,
}
excess := make(map[string]int64)
getFromExcess := func(qty int64, chemical string) int64 {
inStock := excess[chemical]
qty -= inStock
excess[chemical] = int64(math.Min(math.Abs(float64(qty)), 0))
return inStock - excess[chemical]
}
for len(needs) > 0 {
keys := u.MapKeys(needs)
chemical := keys[len(keys)-1]
qtyRequired := needs[chemical]
delete(needs, chemical)
fromExcess := getFromExcess(qtyRequired, chemical)
qtyRequired -= fromExcess
reaction := d.getReactionProducing(chemical)
qtyProduced := int64(reaction.output.Second)
ingredients := reaction.inputs
n := int64(math.Ceil(float64(qtyRequired) / float64(qtyProduced)))
excess[chemical] = (qtyProduced * n) - qtyRequired
for ingredient, qtyIngredient := range ingredients {
if ingredient == "ORE" {
oreRequired += int64(int64(qtyIngredient) * n)
} else {
needs[ingredient] += int64(qtyIngredient) * n
}
}
}
return oreRequired
}
func (d *Day14) Part1() string {
neededOre := d.oreRequiredStock(1)
return fmt.Sprintf("%s%d%s", u.TextBold, neededOre, u.TextReset)
}
func (d *Day14) Part2() string {
oreAvailable := int64(1000000000000)
estimate := oreAvailable / d.oreRequiredStock(1)
high := estimate * 2
low := estimate
lastSuccess := low
lastFailure := high
fuelProduced := low
for math.Abs(float64(lastFailure)-float64(lastSuccess)) > 1 {
oreConsumed := d.oreRequiredStock(fuelProduced)
if oreConsumed < oreAvailable {
lastSuccess = fuelProduced
fuelProduced += (lastFailure - lastSuccess) / 2
} else {
lastFailure = fuelProduced
fuelProduced -= (lastFailure - lastSuccess) / 2
}
}
return fmt.Sprintf("%s%d%s", u.TextBold, lastSuccess, u.TextReset)
}