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 tweaking and rewriting until I landed on this which wasn't far off from what I was trying to do previously, but I had been 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:
113
days/14.go
Normal file
113
days/14.go
Normal file
@ -0,0 +1,113 @@
|
||||
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)
|
||||
lastSuccess := u.Bisect(estimate, estimate*2, 1, func(val int64) bool {
|
||||
oreConsumed := d.getOreRequiredForFuel(val)
|
||||
return oreConsumed < oreAvailable
|
||||
})
|
||||
|
||||
return fmt.Sprintf("Maximum fuel we can make from 1 trillion ore: %s%d%s", u.TextBold, lastSuccess, u.TextReset)
|
||||
}
|
56
inputs/14p.txt
Normal file
56
inputs/14p.txt
Normal file
@ -0,0 +1,56 @@
|
||||
6 WBVJ, 16 CDVNK => 2 PJBZT
|
||||
135 ORE => 8 MWDXJ
|
||||
27 NBRHT, 2 NSWK, 2 CMHMQ, 29 NFCB, 11 KNGJ, 12 MGCKC, 56 NHTKL, 7 WNFSV => 1 FUEL
|
||||
1 SFJFX, 3 MXNK => 4 NLSBZ
|
||||
2 PFKRW, 1 VXFRX, 22 QDJCL => 6 GBDG
|
||||
7 TSTF, 4 ZLJN => 7 DMWS
|
||||
5 KPCF, 1 DLMDJ, 1 FNWGH => 6 TSTF
|
||||
8 DTWKS, 1 GBDG => 4 CGZQ
|
||||
26 CNWZM, 4 KPCF => 3 DTWKS
|
||||
1 JVLHM, 7 DTWKS, 7 PJBZT => 8 MRPHV
|
||||
2 MWDXJ => 3 VHFPC
|
||||
1 WXNW, 6 PFKRW => 7 ZVGVP
|
||||
2 ZVGVP => 1 CMHMQ
|
||||
8 JVLHM, 11 XRKN, 1 HCGKZ => 8 CHZLX
|
||||
20 TSTF => 4 XDZMZ
|
||||
3 CMHMQ, 7 ZVGVP, 10 XRKN => 9 FNWGH
|
||||
12 HCGKZ, 4 NLSBZ, 15 RWRDP, 4 MRPHV, 31 KRDV, 6 PMXK, 2 NFVZ => 7 KNGJ
|
||||
1 TXZCM => 9 BMPJ
|
||||
2 ZFXQ => 3 NBRHT
|
||||
13 JVLHM, 1 VHFPC => 3 PBJPZ
|
||||
7 HCGKZ => 7 PMXK
|
||||
2 RWRDP, 3 VSTQ, 12 PMXK => 7 MXNK
|
||||
1 PJBZT, 3 QRSK => 1 KRDV
|
||||
1 MGCKC, 6 CMHMQ => 6 PQTVS
|
||||
1 TNHCS, 24 ZLJN => 4 RWRDP
|
||||
5 MWDXJ, 1 WXNW => 9 QBCLF
|
||||
1 ZFXQ, 1 DLMDJ => 4 DJXRM
|
||||
1 ZFXQ => 2 CNWZM
|
||||
1 KPCF => 6 ZXDVF
|
||||
2 MRPHV => 1 GSTG
|
||||
5 BMPJ, 2 ZLJN => 8 XQJZ
|
||||
1 MWDXJ, 1 ZVGVP => 3 CDVNK
|
||||
3 NFCB, 3 CMHMQ, 1 MWDXJ => 4 XRKN
|
||||
1 WXNW, 1 TXZCM => 5 ZLJN
|
||||
4 ZXDVF => 4 WBVJ
|
||||
2 GBDG => 4 KPCF
|
||||
4 CHZLX, 7 ZFXQ, 14 PQTVS => 9 VSTQ
|
||||
3 TXZCM, 7 ZLJN, 7 ZXDVF => 9 JVLHM
|
||||
1 DMWS, 3 TSTF => 5 HCGKZ
|
||||
2 CGZQ => 4 NFVZ
|
||||
2 PQTVS, 9 VMNJ => 9 TXZCM
|
||||
3 KPCF => 4 DLMDJ
|
||||
7 VMNJ, 24 XQJZ, 7 GSTG, 8 NLSBZ, 10 MGCKC, 2 SFJFX, 18 BMPJ => 1 NSWK
|
||||
41 CNWZM, 5 DJXRM, 1 QRSK, 1 KPCF, 15 XDZMZ, 3 MRPHV, 1 NLSBZ, 9 KRDV => 2 WNFSV
|
||||
10 PBJPZ, 29 BMPJ, 2 PMXK => 7 SFJFX
|
||||
116 ORE => 4 WXNW
|
||||
2 CNWZM => 2 TNHCS
|
||||
10 QBCLF => 7 NFCB
|
||||
1 QBCLF => 2 ZFXQ
|
||||
15 ZLJN => 7 QRSK
|
||||
183 ORE => 3 QDJCL
|
||||
11 GBDG => 5 VMNJ
|
||||
4 DMWS, 3 QRSK => 3 NHTKL
|
||||
124 ORE => 6 VXFRX
|
||||
1 MWDXJ => 6 MGCKC
|
||||
108 ORE => 9 PFKRW
|
6
inputs/14s1.txt
Normal file
6
inputs/14s1.txt
Normal file
@ -0,0 +1,6 @@
|
||||
10 ORE => 10 A
|
||||
1 ORE => 1 B
|
||||
7 A, 1 B => 1 C
|
||||
7 A, 1 C => 1 D
|
||||
7 A, 1 D => 1 E
|
||||
7 A, 1 E => 1 FUEL
|
7
inputs/14s2.txt
Normal file
7
inputs/14s2.txt
Normal file
@ -0,0 +1,7 @@
|
||||
9 ORE => 2 A
|
||||
8 ORE => 3 B
|
||||
7 ORE => 5 C
|
||||
3 A, 4 B => 1 AB
|
||||
5 B, 7 C => 1 BC
|
||||
4 C, 1 A => 1 CA
|
||||
2 AB, 3 BC, 4 CA => 1 FUEL
|
9
inputs/14s3.txt
Normal file
9
inputs/14s3.txt
Normal file
@ -0,0 +1,9 @@
|
||||
157 ORE => 5 NZVS
|
||||
165 ORE => 6 DCFZ
|
||||
44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL
|
||||
12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ
|
||||
179 ORE => 7 PSHF
|
||||
177 ORE => 5 HKGWZ
|
||||
7 DCFZ, 7 PSHF => 2 XJWVT
|
||||
165 ORE => 2 GPVTF
|
||||
3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT
|
12
inputs/14s4.txt
Normal file
12
inputs/14s4.txt
Normal file
@ -0,0 +1,12 @@
|
||||
2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG
|
||||
17 NVRVD, 3 JNWZP => 8 VPVL
|
||||
53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL
|
||||
22 VJHF, 37 MNCFX => 5 FWMGM
|
||||
139 ORE => 4 NVRVD
|
||||
144 ORE => 7 JNWZP
|
||||
5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC
|
||||
5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV
|
||||
145 ORE => 6 MNCFX
|
||||
1 NVRVD => 8 CXFTF
|
||||
1 VJHF, 6 MNCFX => 4 RFSQX
|
||||
176 ORE => 6 VJHF
|
17
inputs/14s5.txt
Normal file
17
inputs/14s5.txt
Normal file
@ -0,0 +1,17 @@
|
||||
171 ORE => 8 CNZTR
|
||||
7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL
|
||||
114 ORE => 4 BHXH
|
||||
14 VRPVC => 6 BMBT
|
||||
6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL
|
||||
6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT
|
||||
15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW
|
||||
13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW
|
||||
5 BMBT => 4 WPTQ
|
||||
189 ORE => 9 KTJDG
|
||||
1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP
|
||||
12 VRPVC, 27 CNZTR => 2 XDBXC
|
||||
15 KTJDG, 12 BHXH => 5 XCVML
|
||||
3 BHXH, 2 VRPVC => 7 MZWV
|
||||
121 ORE => 7 VRPVC
|
||||
7 XCVML => 6 RJRHP
|
||||
5 BHXH, 4 VRPVC => 5 LTCX
|
1
main.go
1
main.go
@ -44,6 +44,7 @@ var dayMap = []day{
|
||||
&days.Day11{},
|
||||
&days.Day12{},
|
||||
&days.Day13{},
|
||||
&days.Day14{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
26
utilities/bisect.go
Normal file
26
utilities/bisect.go
Normal file
@ -0,0 +1,26 @@
|
||||
package utilities
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Bisect takes a known-good low and known-bad high value as the bounds
|
||||
// to bisect, and a function to test each value for success or failure.
|
||||
// If the function succeeds, the value is adjusted toward the maximum,
|
||||
// and if the function fails, the value is adjusted toward the minimum.
|
||||
// The final value is returned when the difference between the success
|
||||
// and the failure is less than or equal to the acceptance threshold
|
||||
// (usually 1, for integers).
|
||||
func Bisect[T Number](low, high, threshold T, tryFunc func(val T) bool) T {
|
||||
for T(math.Abs(float64(high-low))) > threshold {
|
||||
currVal := low + ((high - low) / 2)
|
||||
success := tryFunc(currVal)
|
||||
if success {
|
||||
low = currVal
|
||||
} else {
|
||||
high = currVal
|
||||
}
|
||||
}
|
||||
|
||||
return low
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package utilities
|
||||
|
||||
import "math"
|
||||
|
||||
func GCD[T Integer](a, b T) T {
|
||||
if b == 0 {
|
||||
return a
|
||||
@ -25,3 +27,23 @@ func LCM[T Integer](nums ...T) uint64 {
|
||||
func lcm[T Integer](a, b T) uint64 {
|
||||
return uint64(a*b) / uint64(GCD(a, b))
|
||||
}
|
||||
|
||||
func Min[T Number](nums ...T) T {
|
||||
numNums := len(nums)
|
||||
if numNums == 2 {
|
||||
return T(math.Min(float64(nums[0]), float64(nums[1])))
|
||||
}
|
||||
|
||||
if numNums == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
least := nums[0]
|
||||
for i := 1; i < numNums; i++ {
|
||||
if nums[i] < least {
|
||||
least = nums[i]
|
||||
}
|
||||
}
|
||||
|
||||
return least
|
||||
}
|
||||
|
Reference in New Issue
Block a user