This commit is contained in:
Eric Mertens
2022-11-18 21:09:04 -08:00
parent 8f772d85b2
commit c6879b4359
6 changed files with 237 additions and 110 deletions

View File

@@ -3,6 +3,7 @@
#include <cstdint>
#include <deque>
#include <iostream>
#include <sstream>
#include <iterator>
#include <map>
#include <optional>
@@ -16,6 +17,7 @@
namespace {
using Name = std::string;
using Value = std::int64_t;
@@ -23,6 +25,10 @@ struct Literal { Value value; };
struct Variable { Name name; };
using Expression = std::variant<Literal, Variable>;
struct Send { Value sent; };
struct Receive { Value & target; };
using Effect = std::variant<Send, Receive>;
struct Set { Name x; Expression y; };
struct Add { Name x; Expression y; };
struct Mul { Name x; Expression y; };
@@ -32,6 +38,11 @@ struct Rcv { Name x; };
struct Snd { Expression x; };
using Instruction = std::variant<Set, Add, Mul, Mod, Jgz, Rcv, Snd>;
struct State {
std::map<Name, Value> registers;
std::size_t pc;
};
auto IsName(std::string const& word) {
return std::all_of(word.begin(), word.end(), [](auto c) { return std::isalpha(c); });
}
@@ -92,113 +103,140 @@ auto Parse(std::istream & in) -> std::vector<Instruction> {
return result;
}
struct Send { Value sent; };
struct Receive { Value & target; };
struct Halt {};
using Effect = std::variant<Send, Receive, Halt>;
auto Eval(State const& state, Expression expression) -> Value {
return std::visit(overloaded{
[](Literal l) -> Value { return l.value; },
[&](Variable v) -> Value {
if (auto it = state.registers.find(v.name); it != state.registers.end()) {
return it->second;
} else {
return 0; }
},
}, expression);
}
struct Machine {
std::vector<Instruction> const& program_;
std::map<Name, Value> registers_;
std::size_t pc_;
auto Step(State & state, Instruction const& instruction) -> std::optional<Effect> {
using R = std::optional<Effect>;
return std::visit(overloaded{
[&](Set instruction) -> R { state.registers[instruction.x] = Eval(state, instruction.y); state.pc++; return {}; },
[&](Add instruction) -> R { state.registers[instruction.x] += Eval(state, instruction.y); state.pc++; return {}; },
[&](Mul instruction) -> R { state.registers[instruction.x] *= Eval(state, instruction.y); state.pc++; return {}; },
[&](Mod instruction) -> R { state.registers[instruction.x] %= Eval(state, instruction.y); state.pc++; return {}; },
[&](Jgz instruction) -> R { state.pc += Eval(state, instruction.x) > 0 ? Eval(state, instruction.y) : 1; return {}; },
[&](Snd instruction) -> R { state.pc++; return Send{Eval(state, instruction.x)}; },
[&](Rcv instruction) -> R { state.pc++; return Receive{state.registers[instruction.x]}; },
},
instruction);
}
public:
Machine(std::vector<Instruction> const& program)
: program_{program}, registers_{}, pc_{}
{}
auto Eval(Expression expression) -> Value {
return std::visit(overloaded{
[](Literal l) { return l.value; },
[&](Variable v) { return registers_[v.name]; },
}, expression);
}
auto Step() -> Effect {
while (pc_ < program_.size()) {
if (auto effect = std::visit(overloaded{
[&](Set instruction) -> std::optional<Effect> { registers_[instruction.x] = Eval(instruction.y); pc_++; return {}; },
[&](Add instruction) -> std::optional<Effect> { registers_[instruction.x] += Eval(instruction.y); pc_++; return {}; },
[&](Mul instruction) -> std::optional<Effect> { registers_[instruction.x] *= Eval(instruction.y); pc_++; return {}; },
[&](Mod instruction) -> std::optional<Effect> { registers_[instruction.x] %= Eval(instruction.y); pc_++; return {}; },
[&](Jgz instruction) -> std::optional<Effect> { pc_ += Eval(instruction.x) > 0 ? Eval(instruction.y) : 1; return {}; },
[&](Snd instruction) -> std::optional<Effect> { pc_++; return Send{Eval(instruction.x)}; },
[&](Rcv instruction) -> std::optional<Effect> { pc_++; return Receive{registers_[instruction.x]}; },
},
program_[pc_]))
{
return *effect;
}
auto BigStep(State & state, std::vector<Instruction> const& program) -> Effect {
while(state.pc < program.size()) {
if (auto effect = Step(state, program[state.pc])) {
return *effect;
}
return Halt{};
}
};
throw std::runtime_error{"program crash"};
}
auto Part1(std::vector<Instruction> const& program) -> Value {
Value last_sound = -1;
Machine m { program };
State state {};
for(;;) {
auto effect = m.Step();
for (;;) {
auto effect = BigStep(state, program);
switch (effect.index()) {
case 0: last_sound = std::get<0>(effect).sent; break;
case 1: if (std::get<1>(effect).target > 0) { return last_sound; } break;
default: throw std::runtime_error{"program halted"};
}
}
throw std::runtime_error{"part 1 failed"};
}
auto Spin(Machine & m, std::deque<Value> & input, std::deque<Value> & output) -> Value* {
auto Spin(
State & state,
std::vector<Instruction> const& program,
std::deque<Value> & input,
std::deque<Value> & output
) -> std::pair<Value*, std::size_t>
{
std::size_t n = 0;
for(;;) {
auto effect = m.Step();
auto effect = BigStep(state, program);
switch (effect.index()) {
case 0: output.push_back(std::get<0>(effect).sent); break;
case 0:
output.push_back(std::get<0>(effect).sent);
n++;
break;
case 1: {
auto & target = std::get<1>(effect).target;
if (input.empty()) {
return &target;
return {&target, n};
} else {
target = input.front();
input.pop_front();
}
break;
}
default: throw std::runtime_error{"program halted"};
}
}
}
auto Part2(std::vector<Instruction> const& program) -> std::size_t {
Machine m0 { program };
Machine m1 { program };
m1.registers_["p"] = 1;
State m0{};
State m1{{{"p",1}}};
std::deque<Value> inputs0;
std::deque<Value> inputs1;
auto stuck0 = Spin(m0, inputs0, inputs1);
auto stuck1 = Spin(m1, inputs1, inputs0);
std::size_t result = inputs0.size();
auto [stuck0, _] = Spin(m0, program, inputs0, inputs1);
auto [stuck1, n] = Spin(m1, program, inputs1, inputs0);
while(!(inputs0.empty() && inputs1.empty())) {
if (!inputs0.empty()) {
*stuck0 = inputs0.front(); inputs0.pop_front();
stuck0 = Spin(m0, inputs0, inputs1);
stuck0 = Spin(m0, program, inputs0, inputs1).first;
}
if (!inputs1.empty()) {
*stuck1 = inputs1.front(); inputs1.pop_front();
result -= inputs0.size();
stuck1 = Spin(m1, inputs1, inputs0);
result += inputs0.size();
auto res = Spin(m1, program, inputs1, inputs0);
stuck1 = res.first;
n += res.second;
}
}
return result;
return n;
}
} // namespace
TEST_SUITE("documented examples") {
TEST_CASE("part 1") {
std::istringstream in {
"set a 1\n"
"add a 2\n"
"mul a a\n"
"mod a 5\n"
"snd a\n"
"set a 0\n"
"rcv a\n"
"jgz a -1\n"
"set a 1\n"
"jgz a -2\n"};
REQUIRE(Part1(Parse(in)) == 4);
}
TEST_CASE("part 2") {
std::istringstream in {
"snd 1\n"
"snd 2\n"
"snd p\n"
"rcv a\n"
"rcv b\n"
"rcv c\n"
"rcv d\n"};
REQUIRE(Part2(Parse(in)) == 3);
}
}
auto main(int argc, char** argv) -> int {
auto program = Parse(aocpp::Startup(argc, argv));
std::cout << "Part 1: " << Part1(program) << std::endl;