159 lines
4.4 KiB
C++
159 lines
4.4 KiB
C++
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include <doctest.h>
|
|
|
|
#include <aocpp/Startup.hpp>
|
|
|
|
namespace {
|
|
|
|
auto Eval(
|
|
std::string const& input,
|
|
std::map<char, std::pair<int, std::function<std::int64_t(std::int64_t,std::int64_t)>>> cfg
|
|
) -> std::int64_t
|
|
{
|
|
std::vector<char> ops;
|
|
std::vector<std::int64_t> vals;
|
|
|
|
auto const eval1 = [&](){
|
|
if (ops.size() < 1 || vals.size() < 2) {
|
|
throw std::runtime_error{"parse error"};
|
|
}
|
|
if (auto opit = cfg.find(ops.back()); opit != cfg.end()) {
|
|
std::int64_t y = vals.back(); vals.pop_back();
|
|
std::int64_t x = vals.back(); vals.pop_back();
|
|
vals.push_back(opit->second.second(x,y));
|
|
ops.pop_back();
|
|
} else {
|
|
throw std::runtime_error{"parse error"};
|
|
}
|
|
};
|
|
|
|
bool expect_operator = false;
|
|
|
|
auto it = input.begin();
|
|
while (it != input.end()) {
|
|
char const c = *it++;
|
|
|
|
// skip whitespace
|
|
if (c == ' ') continue;
|
|
|
|
// number literal
|
|
if (std::isdigit(c)) {
|
|
if (expect_operator) { throw std::runtime_error{"unexpected literal"}; }
|
|
expect_operator = true;
|
|
|
|
std::int64_t acc = c - '0';
|
|
while (it != input.end() && std::isdigit(*it)) {
|
|
acc = acc*10 + (*it++ - '0');
|
|
}
|
|
vals.push_back(acc);
|
|
continue;
|
|
}
|
|
|
|
// begin subexpression
|
|
if ('(' == c) {
|
|
if (expect_operator) { throw std::runtime_error{"unexpected '('"}; }
|
|
ops.push_back('(');
|
|
continue;
|
|
}
|
|
|
|
// end subexpression
|
|
if (')' == c) {
|
|
if (!expect_operator) { throw std::runtime_error{"unexpected ')'"}; }
|
|
while (!ops.empty() && ops.back() != '(') eval1();
|
|
if (ops.empty()) throw std::runtime_error{"parse error"};
|
|
ops.pop_back(); // '('
|
|
continue;
|
|
}
|
|
|
|
// binary operator
|
|
if (auto opit = cfg.find(c); opit != cfg.end()) {
|
|
if (!expect_operator) { throw std::runtime_error{"unexpected operator"}; }
|
|
expect_operator = false;
|
|
while (!ops.empty() && cfg[ops.back()].first >= opit->second.first) eval1();
|
|
ops.push_back(c);
|
|
continue;
|
|
}
|
|
|
|
throw std::runtime_error{"unknown character"};
|
|
}
|
|
|
|
while (!ops.empty()) eval1();
|
|
|
|
if (vals.empty()) { throw std::runtime_error{"empty input"}; }
|
|
|
|
return vals.back();
|
|
}
|
|
|
|
auto Part1(std::string const& line) {
|
|
return Eval(line, {{'+', {1, std::plus<std::int64_t>()}},{'*', {1, std::multiplies<std::int64_t>()}}});
|
|
}
|
|
|
|
auto Part2(std::string const& line) {
|
|
return Eval(line, {{'+', {2, std::plus<std::int64_t>()}},{'*', {1, std::multiplies<std::int64_t>()}}});
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_SUITE("documented examples") {
|
|
|
|
TEST_CASE("part 1") {
|
|
CHECK(Part1("1 + 2 * 3 + 4 * 5 + 6") == 71);
|
|
CHECK(Part1("1 + (2 * 3) + (4 * (5 + 6))") == 51);
|
|
CHECK(Part1("2 * 3 + (4 * 5)") == 26);
|
|
CHECK(Part1("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 437);
|
|
CHECK(Part1("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 12240);
|
|
CHECK(Part1("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 13632);
|
|
}
|
|
|
|
TEST_CASE("part 2") {
|
|
CHECK(Part2("1 + 2 * 3 + 4 * 5 + 6") == 231);
|
|
CHECK(Part2("1 + (2 * 3) + (4 * (5 + 6))") == 51);
|
|
CHECK(Part2("2 * 3 + (4 * 5)") == 46);
|
|
CHECK(Part2("5 + (8 * 3 + 9 + 3 * 4 * 3)") == 1445);
|
|
CHECK(Part2("5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))") == 669060);
|
|
CHECK(Part2("((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2") == 23340);
|
|
}
|
|
|
|
TEST_CASE("errors") {
|
|
CHECK_THROWS_AS(Part1(""), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 + ()"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("(1"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 (2)"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1(")"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1)"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 2"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 +"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("+ 1"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 /"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 / 2"), std::runtime_error);
|
|
CHECK_THROWS_AS(Part1("1 + * 2"), std::runtime_error);
|
|
}
|
|
|
|
}
|
|
|
|
auto main(int argc, char** argv) -> int {
|
|
auto const in_ptr = aocpp::Startup(argc, argv);
|
|
auto & in = *in_ptr;
|
|
|
|
std::int64_t part1 = 0;
|
|
std::int64_t part2 = 0;
|
|
|
|
std::string line;
|
|
while (std::getline(in, line)) {
|
|
part1 += Part1(line);
|
|
part2 += Part2(line);
|
|
}
|
|
|
|
std::cout << "Part 1: " << part1 << std::endl;
|
|
std::cout << "Part 2: " << part2 << std::endl;
|
|
}
|