This one's part 1 destroyed me. I had a very difficult time, trying 3 separate approaches, each one of which worked for most cases but eventually fell apart on the 5th sample or my actual puzzle input. I ended up reading a bunch of hints from the subreddit which eventually led me to a blog post describing this solution, which wasn't far off from what I had, but I was overcomplicating things. Part 2 surprised me in that I expected a simple "ore available divided by ore needed for 1 fuel" would solve it, but of course the excess chemicals produced in any given reaction meant that it wasn't that simple. So this approach uses that estimate as a lower bound, since it always underestimates, and then bisects its way to the solution (starting at the lower bound and adding 1 each time took too long). I'm sure a smarter upper bound choice could lower the runtime of this by a bit, but runtime isn't bad enough right now for me to try any additional optimizations.
130 lines
2.8 KiB
Go
130 lines
2.8 KiB
Go
package days
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
u "parnic.com/aoc2019/utilities"
|
|
)
|
|
|
|
type reaction struct {
|
|
inputs map[string]int64
|
|
output u.Pair[string, int]
|
|
}
|
|
|
|
type Day14 struct {
|
|
reactions []reaction
|
|
}
|
|
|
|
func (d *Day14) Parse() {
|
|
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]int64)
|
|
for _, input := range inputs {
|
|
pair := strings.Split(input, " ")
|
|
d.reactions[i].inputs[pair[1]], _ = strconv.ParseInt(pair[0], 10, 64)
|
|
}
|
|
}
|
|
}
|
|
|
|
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) getOreRequiredForFuel(qty int64) int64 {
|
|
oreRequired := int64(0)
|
|
needs := map[string]int64{
|
|
"FUEL": qty,
|
|
}
|
|
excess := make(map[string]int64)
|
|
|
|
getFromExcess := func(qty int64, chemical string) int64 {
|
|
available := u.Min(excess[chemical], qty)
|
|
excess[chemical] -= available
|
|
return available
|
|
}
|
|
|
|
for len(needs) > 0 {
|
|
keys := u.MapKeys(needs)
|
|
producing := keys[0]
|
|
qtyRequired := needs[producing]
|
|
delete(needs, producing)
|
|
|
|
fromExcess := getFromExcess(qtyRequired, producing)
|
|
if fromExcess == qtyRequired {
|
|
continue
|
|
}
|
|
qtyRequired -= fromExcess
|
|
|
|
reaction := d.getReactionProducing(producing)
|
|
|
|
qtyProduced := int64(reaction.output.Second)
|
|
reactionsNeeded := int64(math.Ceil(float64(qtyRequired) / float64(qtyProduced)))
|
|
|
|
excess[producing] = (qtyProduced * reactionsNeeded) - qtyRequired
|
|
|
|
for reagent, inputQty := range reaction.inputs {
|
|
qtyNeeded := inputQty * reactionsNeeded
|
|
if reagent == "ORE" {
|
|
oreRequired += qtyNeeded
|
|
} else {
|
|
needs[reagent] += qtyNeeded
|
|
}
|
|
}
|
|
}
|
|
|
|
return oreRequired
|
|
}
|
|
|
|
func (d *Day14) Part1() string {
|
|
neededOre := d.getOreRequiredForFuel(1)
|
|
return fmt.Sprintf("%s%d%s", u.TextBold, neededOre, u.TextReset)
|
|
}
|
|
|
|
func (d *Day14) Part2() string {
|
|
oreAvailable := int64(1000000000000)
|
|
estimate := oreAvailable / d.getOreRequiredForFuel(1)
|
|
|
|
high := estimate * 2
|
|
low := estimate
|
|
|
|
lastSuccess := low
|
|
lastFailure := high
|
|
fuelProduced := low
|
|
|
|
for math.Abs(float64(lastFailure-lastSuccess)) > 1 {
|
|
oreConsumed := d.getOreRequiredForFuel(fuelProduced)
|
|
adjustment := (lastFailure - lastSuccess) / 2
|
|
if oreConsumed < oreAvailable {
|
|
lastSuccess = fuelProduced
|
|
} else {
|
|
lastFailure = fuelProduced
|
|
adjustment = -adjustment
|
|
}
|
|
|
|
fuelProduced += adjustment
|
|
}
|
|
|
|
return fmt.Sprintf("%s%d%s", u.TextBold, lastSuccess, u.TextReset)
|
|
}
|