Day 14 solution
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.
This commit is contained in:
129
days/14.go
Normal file
129
days/14.go
Normal file
@ -0,0 +1,129 @@
|
||||
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("Minimum ore to produce 1 FUEL: %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("Maximum fuel we can make from 1 trillion ore: %s%d%s", u.TextBold, lastSuccess, u.TextReset)
|
||||
}
|
Reference in New Issue
Block a user