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.Day11{},
|
||||||
&days.Day12{},
|
&days.Day12{},
|
||||||
&days.Day13{},
|
&days.Day13{},
|
||||||
|
&days.Day14{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
package utilities
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
func GCD[T Integer](a, b T) T {
|
func GCD[T Integer](a, b T) T {
|
||||||
if b == 0 {
|
if b == 0 {
|
||||||
return a
|
return a
|
||||||
@ -25,3 +27,23 @@ func LCM[T Integer](nums ...T) uint64 {
|
|||||||
func lcm[T Integer](a, b T) uint64 {
|
func lcm[T Integer](a, b T) uint64 {
|
||||||
return uint64(a*b) / uint64(GCD(a, b))
|
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