2022-11-08 21:28:12 -08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <iterator>
|
|
|
|
#include <numeric>
|
|
|
|
#include <map>
|
|
|
|
#include <set>
|
|
|
|
#include <vector>
|
|
|
|
#include <tuple>
|
|
|
|
|
2022-11-16 09:28:30 -08:00
|
|
|
#include <doctest.h>
|
|
|
|
|
2022-11-08 21:28:12 -08:00
|
|
|
#include <aocpp/Startup.hpp>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
using Recipes = std::map<std::string, std::pair<std::int64_t, std::vector<std::pair<std::int64_t, std::string>>>>;
|
|
|
|
|
|
|
|
auto Parse(std::istream & in) -> Recipes {
|
|
|
|
Recipes result;
|
|
|
|
|
|
|
|
std::string line;
|
|
|
|
|
|
|
|
std::int64_t n;
|
|
|
|
std::string word;
|
|
|
|
|
|
|
|
while (std::getline(in, line)) {
|
|
|
|
std::vector<std::pair<std::int64_t, std::string>> components;
|
|
|
|
std::istringstream sin {line};
|
|
|
|
|
|
|
|
bool leftSide;
|
|
|
|
do {
|
|
|
|
sin >> n >> word;
|
|
|
|
leftSide = word.back() == ',';
|
|
|
|
if (leftSide) word.pop_back();
|
|
|
|
components.push_back({n, word});
|
|
|
|
} while(leftSide);
|
|
|
|
sin >> word; // skip
|
|
|
|
sin >> n >> word;
|
|
|
|
result.insert({word, {n, std::move(components)}});
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto Topsort(Recipes const& recipes) -> std::vector<std::string>
|
|
|
|
{
|
|
|
|
// Kahn's algorithm
|
|
|
|
std::vector<std::string> result;
|
2022-11-09 15:45:55 -08:00
|
|
|
result.reserve(recipes.size());
|
|
|
|
|
|
|
|
std::map<std::string, bool> marks; // false temp, true done
|
|
|
|
auto const visit = [&](auto const& visit, auto const& name) -> void {
|
|
|
|
auto [mark_iterator, is_new] = marks.try_emplace(name, false);
|
|
|
|
auto& [_, is_done] = *mark_iterator;
|
|
|
|
|
|
|
|
if (is_new) {
|
|
|
|
auto const rit = recipes.find(name);
|
|
|
|
if (rit != recipes.end()) {
|
|
|
|
for (auto const& [_, c] : rit->second.second) {
|
|
|
|
visit(visit, c);
|
2022-11-08 21:28:12 -08:00
|
|
|
}
|
|
|
|
}
|
2022-11-09 15:45:55 -08:00
|
|
|
is_done = true;
|
2022-11-08 21:28:12 -08:00
|
|
|
result.push_back(name);
|
2022-11-09 15:45:55 -08:00
|
|
|
} else if (!is_done) {
|
|
|
|
throw std::runtime_error{"cyclic graph"};
|
2022-11-08 21:28:12 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-09 15:45:55 -08:00
|
|
|
for (auto const& [c,_] : recipes) {
|
|
|
|
visit(visit, c);
|
2022-11-08 21:28:12 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::reverse(result.begin(), result.end());
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Machine {
|
|
|
|
struct Entry {
|
|
|
|
std::int64_t need;
|
|
|
|
std::int64_t batch;
|
|
|
|
std::vector<std::pair<std::int64_t, std::int64_t&>> components;
|
|
|
|
};
|
|
|
|
std::int64_t ore_;
|
|
|
|
std::vector<Entry> process_;
|
|
|
|
public:
|
|
|
|
Machine(Recipes const& recipes)
|
|
|
|
{
|
|
|
|
auto order = Topsort(recipes);
|
|
|
|
order.pop_back();
|
|
|
|
process_.resize(order.size(), {});
|
|
|
|
|
|
|
|
std::map<std::string, std::size_t> indexes;
|
|
|
|
for (auto && name : order) {
|
|
|
|
indexes.insert({name, indexes.size()});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto && [k,v] : recipes) {
|
|
|
|
auto & entry = process_[indexes[k]];
|
|
|
|
entry.batch = v.first;
|
|
|
|
for (auto && [m,c] : v.second) {
|
|
|
|
entry.components.push_back({m, c == "ORE" ? ore_ : process_[indexes[c]].need});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto operator()(std::int64_t fuel) {
|
|
|
|
for (auto && entry : process_) {
|
|
|
|
entry.need = 0;
|
|
|
|
}
|
|
|
|
process_[0].need = fuel;
|
|
|
|
ore_ = 0;
|
|
|
|
|
|
|
|
for (auto && entry : process_) {
|
|
|
|
auto batches = (entry.need + entry.batch - 1) / entry.batch;
|
|
|
|
for (auto && [m,c] : entry.components) {
|
|
|
|
c += batches*m;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ore_;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
auto ComputeFuel(Machine & machine, std::int64_t ore) {
|
|
|
|
std::int64_t tooHi = 1;
|
|
|
|
while (machine(tooHi) <= ore) {
|
|
|
|
tooHi *= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::int64_t lo = 0;
|
|
|
|
while (lo+1 != tooHi) {
|
|
|
|
auto mid = std::midpoint(lo, tooHi);
|
|
|
|
(machine(mid) > ore ? tooHi : lo) = mid;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lo;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2022-11-16 09:28:30 -08:00
|
|
|
TEST_SUITE("documented examples") {
|
|
|
|
TEST_CASE("a") {
|
|
|
|
std::istringstream in {
|
|
|
|
"10 ORE => 10 A\n"
|
|
|
|
"1 ORE => 1 B\n"
|
|
|
|
"7 A, 1 B => 1 C\n"
|
|
|
|
"7 A, 1 C => 1 D\n"
|
|
|
|
"7 A, 1 D => 1 E\n"
|
|
|
|
"7 A, 1 E => 1 FUEL\n"};
|
|
|
|
Machine m{Parse(in)};
|
|
|
|
REQUIRE(m(1) == 31);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("b") {
|
|
|
|
std::istringstream in {
|
|
|
|
"9 ORE => 2 A\n"
|
|
|
|
"8 ORE => 3 B\n"
|
|
|
|
"7 ORE => 5 C\n"
|
|
|
|
"3 A, 4 B => 1 AB\n"
|
|
|
|
"5 B, 7 C => 1 BC\n"
|
|
|
|
"4 C, 1 A => 1 CA\n"
|
|
|
|
"2 AB, 3 BC, 4 CA => 1 FUEL\n"};
|
|
|
|
Machine m{Parse(in)};
|
|
|
|
REQUIRE(m(1) == 165);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("c") {
|
|
|
|
std::istringstream in {
|
|
|
|
"157 ORE => 5 NZVS\n"
|
|
|
|
"165 ORE => 6 DCFZ\n"
|
|
|
|
"44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL\n"
|
|
|
|
"12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ\n"
|
|
|
|
"179 ORE => 7 PSHF\n"
|
|
|
|
"177 ORE => 5 HKGWZ\n"
|
|
|
|
"7 DCFZ, 7 PSHF => 2 XJWVT\n"
|
|
|
|
"165 ORE => 2 GPVTF\n"
|
|
|
|
"3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT\n"};
|
|
|
|
Machine m{Parse(in)};
|
|
|
|
REQUIRE(m(1) == 13312);
|
|
|
|
REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 82892753);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("d") {
|
|
|
|
std::istringstream in {
|
|
|
|
"2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG\n"
|
|
|
|
"17 NVRVD, 3 JNWZP => 8 VPVL\n"
|
|
|
|
"53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL\n"
|
|
|
|
"22 VJHF, 37 MNCFX => 5 FWMGM\n"
|
|
|
|
"139 ORE => 4 NVRVD\n"
|
|
|
|
"144 ORE => 7 JNWZP\n"
|
|
|
|
"5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC\n"
|
|
|
|
"5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV\n"
|
|
|
|
"145 ORE => 6 MNCFX\n"
|
|
|
|
"1 NVRVD => 8 CXFTF\n"
|
|
|
|
"1 VJHF, 6 MNCFX => 4 RFSQX\n"
|
|
|
|
"176 ORE => 6 VJHF\n"};
|
|
|
|
Machine m{Parse(in)};
|
|
|
|
REQUIRE(m(1) == 180697);
|
|
|
|
REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 5586022);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("e") {
|
|
|
|
std::istringstream in {
|
|
|
|
"171 ORE => 8 CNZTR\n"
|
|
|
|
"7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL\n"
|
|
|
|
"114 ORE => 4 BHXH\n"
|
|
|
|
"14 VRPVC => 6 BMBT\n"
|
|
|
|
"6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL\n"
|
|
|
|
"6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT\n"
|
|
|
|
"15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW\n"
|
|
|
|
"13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW\n"
|
|
|
|
"5 BMBT => 4 WPTQ\n"
|
|
|
|
"189 ORE => 9 KTJDG\n"
|
|
|
|
"1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP\n"
|
|
|
|
"12 VRPVC, 27 CNZTR => 2 XDBXC\n"
|
|
|
|
"15 KTJDG, 12 BHXH => 5 XCVML\n"
|
|
|
|
"3 BHXH, 2 VRPVC => 7 MZWV\n"
|
|
|
|
"121 ORE => 7 VRPVC\n"
|
|
|
|
"7 XCVML => 6 RJRHP\n"
|
|
|
|
"5 BHXH, 4 VRPVC => 5 LTCX\n"};
|
|
|
|
Machine m{Parse(in)};
|
|
|
|
REQUIRE(m(1) == 2210736);
|
|
|
|
REQUIRE(ComputeFuel(m, 1'000'000'000'000) == 460664);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:28:12 -08:00
|
|
|
auto main(int argc, char** argv) -> int {
|
2022-11-21 18:49:22 -08:00
|
|
|
auto recipes = Parse(*aocpp::Startup(argc, argv));
|
2022-11-08 21:28:12 -08:00
|
|
|
auto machine = Machine(recipes);
|
|
|
|
auto part1 = machine(1);
|
|
|
|
auto part2 = ComputeFuel(machine, 1'000'000'000'000);
|
|
|
|
std::cout << "Part 1: " << part1 << std::endl;
|
|
|
|
std::cout << "Part 2: " << part2 << std::endl;
|
|
|
|
}
|