1
0
Fork 0
mirror of https://github.com/Luzifer/aoc2019.git synced 2024-10-18 03:04:19 +00:00

Add solution for Day 14

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-12-14 15:06:03 +01:00
parent 1c34ac5186
commit 21d19eb3f1
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
3 changed files with 279 additions and 0 deletions

158
day14.go Normal file
View file

@ -0,0 +1,158 @@
package aoc2019
import (
"bufio"
"io"
"log"
"math"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
)
type day14NanoFactory struct {
reactions map[string]day14Reaction
}
func (d day14NanoFactory) calculateOreForFuel(n int64) int64 {
var (
availableMats = map[string]int64{}
oreUsed int64
)
var produce func(mat string, amount int64)
produce = func(mat string, amount int64) {
var (
reaction = d.reactions[mat]
reactN = int64(math.Ceil(float64(amount) / float64(reaction.yield)))
)
for reqMat, reqAm := range reaction.materials {
// Multiply for requested amount
reqAm *= reactN
if reqMat == "ORE" {
oreUsed += reqAm
continue
}
if availableMats[reqMat] < reqAm {
produce(reqMat, reqAm-availableMats[reqMat])
}
availableMats[reqMat] -= reqAm
}
availableMats[mat] += reactN * reaction.yield
}
produce("FUEL", n)
return oreUsed
}
type day14Reaction struct {
materials map[string]int64
yield int64
factory *day14NanoFactory
}
func day14ParseReactionChain(r io.Reader) (*day14NanoFactory, error) {
var factory = &day14NanoFactory{reactions: make(map[string]day14Reaction)}
parse := func(in string) (int64, string, error) {
var parts = strings.Split(in, " ")
count, err := strconv.ParseInt(parts[0], 10, 64)
return count, parts[1], errors.Wrap(err, "Unable to parse count")
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
var (
sides = strings.Split(scanner.Text(), " => ")
inputs = strings.Split(sides[0], ", ")
)
yield, outMaterial, err := parse(sides[1])
if err != nil {
return nil, errors.Wrap(err, "Unable to parse output side")
}
rea := day14Reaction{factory: factory, yield: yield, materials: make(map[string]int64)}
for _, in := range inputs {
required, inMaterial, err := parse(in)
if err != nil {
return nil, errors.Wrap(err, "Unable to parse input material")
}
rea.materials[inMaterial] = required
}
factory.reactions[outMaterial] = rea
}
return factory, errors.Wrap(scanner.Err(), "Unable to scan input")
}
func solveDay14Part1(inFile string) (int64, error) {
f, err := os.Open(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to open input file")
}
defer f.Close()
factory, err := day14ParseReactionChain(f)
if err != nil {
return 0, errors.Wrap(err, "Unable to parse reaction chain")
}
return factory.calculateOreForFuel(1), nil
}
func solveDay14Part2(inFile string) (int64, error) {
f, err := os.Open(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to open input file")
}
defer f.Close()
factory, err := day14ParseReactionChain(f)
if err != nil {
return 0, errors.Wrap(err, "Unable to parse reaction chain")
}
var (
limit int64 = 1000000000000
orePerFuel = factory.calculateOreForFuel(1)
minBound = limit / orePerFuel
maxBound = minBound * 2
)
for {
var (
test = (minBound + maxBound) / 2
oreUsed = factory.calculateOreForFuel(test)
)
log.Printf("limit=%d used=%d min=%d max=%d test=%d", limit, oreUsed, minBound, maxBound, test)
switch {
case minBound+1 == maxBound && oreUsed < limit:
return test, nil
case oreUsed > limit:
maxBound = test
case oreUsed < limit:
minBound = test
case oreUsed == limit:
// Doubt this will happen but well
return test, nil
}
}
}

57
day14_input.txt Normal file
View file

@ -0,0 +1,57 @@
164 ORE => 2 TLJL
2 VKMKW => 4 DTCB
2 VKMKW, 16 ZKMXZ, 2 TSVN => 3 TSVQX
2 NFJKN, 2 LMVCD, 5 DSQLK => 1 RNRPB
3 NFJKN, 3 TSVQX, 6 VKMKW => 7 FBFQZ
7 ZKMXZ, 1 PVQLR => 4 MBWVZ
3 SHMGH => 4 ZKMXZ
2 MSZWL => 4 QSDC
3 DGFK => 9 TSVN
21 DTCB, 1 DSQLK => 8 DGDGS
1 DGFK, 1 SXNZP, 1 GCHL => 9 JZWH
1 DSQLK, 4 WFDK, 1 BVSL, 1 TZND, 15 HVPMK, 1 NSKX => 3 DSFDZ
1 ZDVCH, 2 PVQLR, 7 VLNX, 4 JTZM, 1 MVLHV, 1 RDBR, 11 MBWVZ => 7 ZTXQ
9 JZWH, 4 BVSL, 2 NFJKN, 26 LMVCD, 3 MKFDR, 2 TGMNG, 1 NTMRX, 12 DGDGS => 4 PBRZF
25 RNRPB => 6 MKFDR
27 ZKMXZ, 4 NFJKN, 1 DTCB => 5 RDBR
2 ZXTQ, 13 KHRFD => 7 JQJGR
3 WFDVM, 18 QSLKV => 5 NSBN
2 ZXTQ, 6 NTMRX => 4 WFDK
1 VKMKW, 14 TSVQX, 10 ZKMXZ => 6 NFJKN
1 NVDL, 1 ZKMXZ, 9 NSKX => 5 ZDVCH
7 QSDC, 1 BVSL => 4 GCHL
1 QSLKV, 13 XRBKF => 5 NTMRX
11 GDPLN => 8 KHRFD
15 VCJSD => 7 LSLP
4 PCHC, 1 SXNZP, 1 JQJGR => 9 KPBPL
18 TGMNG => 4 HVPMK
1 XRBKF, 26 LVLV => 6 WFDVM
9 VCJSD, 14 SXNZP => 4 TGMNG
22 WFDK, 20 FBFQZ => 6 LHJBH
195 ORE => 7 SHMGH
2 VCJSD, 1 XRBKF => 8 QSLKV
8 ZTXNJ, 4 TLJL => 2 MSZWL
2 LMVCD, 9 PVQLR => 4 NSKX
2 TLJL, 1 GJDPC, 8 ZXTQ => 8 PCHC
6 NSBN, 4 JVJV => 9 ZCDZ
155 ORE => 1 GDPLN
1 GDPLN => 4 VKMKW
1 KPBPL => 8 LVLV
30 NSBN, 20 MVLHV => 1 JVJV
1 LVLV => 1 DGFK
7 TSVQX => 6 LMVCD
7 TLJL, 16 MSZWL, 5 KHRFD => 2 ZXTQ
55 MBWVZ, 61 KHRFD, 16 DSFDZ, 40 LHJBH, 6 ZTXQ, 28 JZWH, 1 PBRZF => 1 FUEL
5 JQJGR, 20 VCJSD => 5 MVLHV
1 SHMGH, 1 ZTXNJ => 4 GJDPC
3 XRBKF, 9 QSLKV, 2 WFDK => 5 JTZM
5 GJDPC => 6 VCJSD
1 GJDPC, 7 XRBKF => 4 PVQLR
11 BVSL => 6 SXNZP
104 ORE => 3 ZTXNJ
3 JZWH, 9 HVPMK, 2 GCHL => 6 VLNX
1 LSLP => 6 XRBKF
1 TLJL => 5 BVSL
5 HVPMK => 9 DSQLK
6 FBFQZ, 22 PVQLR, 4 ZCDZ => 1 NVDL
3 JZWH => 1 TZND

64
day14_test.go Normal file
View file

@ -0,0 +1,64 @@
package aoc2019
import (
"strings"
"testing"
)
func TestDay14OreForFuel(t *testing.T) {
in := strings.NewReader("10 ORE => 10 A\n1 ORE => 1 B\n7 A, 1 B => 1 C\n7 A, 1 C => 1 D\n7 A, 1 D => 1 E\n7 A, 1 E => 1 FUEL")
factory, err := day14ParseReactionChain(in)
if err != nil {
t.Fatalf("Parsing reaction chain caused an error: %s", err)
}
if ore := factory.calculateOreForFuel(1); ore != 31 {
t.Errorf("Unexpected amount of ore: exp=31 got=%d", ore)
}
// More complex example
in = strings.NewReader(`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`)
factory, err = day14ParseReactionChain(in)
if err != nil {
t.Fatalf("Parsing complex reaction chain caused an error: %s", err)
}
if ore := factory.calculateOreForFuel(1); ore != 2210736 {
t.Errorf("Unexpected amount of ore: exp=2210736 got=%d", ore)
}
}
func TestCalculateDay14_Part1(t *testing.T) {
count, err := solveDay14Part1("day14_input.txt")
if err != nil {
t.Fatalf("Day 14 solver failed: %s", err)
}
t.Logf("Solution Day 14 Part 1: %d", count)
}
func TestCalculateDay14_Part2(t *testing.T) {
res, err := solveDay14Part2("day14_input.txt")
if err != nil {
t.Fatalf("Day 14 solver failed: %s", err)
}
t.Logf("Solution Day 14 Part 2: %d", res)
}