From 21d19eb3f135b659df1aa7c98bdf54ae94f82062 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sat, 14 Dec 2019 15:06:03 +0100 Subject: [PATCH] Add solution for Day 14 Signed-off-by: Knut Ahlers --- day14.go | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ day14_input.txt | 57 +++++++++++++++++ day14_test.go | 64 ++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 day14.go create mode 100644 day14_input.txt create mode 100644 day14_test.go diff --git a/day14.go b/day14.go new file mode 100644 index 0000000..6fc01a9 --- /dev/null +++ b/day14.go @@ -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 + } + + } +} diff --git a/day14_input.txt b/day14_input.txt new file mode 100644 index 0000000..bfb64ec --- /dev/null +++ b/day14_input.txt @@ -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 diff --git a/day14_test.go b/day14_test.go new file mode 100644 index 0000000..1c4f04a --- /dev/null +++ b/day14_test.go @@ -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) +}