Add solution for "Day 17: Chronospatial Computer", part 2
This commit is contained in:
parent
5201dcf0bc
commit
45ab5f93ec
@ -124,6 +124,14 @@ Dijkstra's algorithm solves part 1, with the weights of each edge being the sum
|
|||||||
|
|
||||||
Since Dijkstra's algorithm cheaply produces shortest distances from the exit to all other vertices in a single run, the solver can use these to quickly traverse the graph to find all minimum paths for part 2. In order to calculate the number of tiles for all these paths, duplicate path segments and crossings have to be detected. The solver attaches the number of tiles per path segment and the connected crossings to each vertex while constructing the graph to facilitate this.
|
Since Dijkstra's algorithm cheaply produces shortest distances from the exit to all other vertices in a single run, the solver can use these to quickly traverse the graph to find all minimum paths for part 2. In order to calculate the number of tiles for all these paths, duplicate path segments and crossings have to be detected. The solver attaches the number of tiles per path segment and the connected crossings to each vertex while constructing the graph to facilitate this.
|
||||||
|
|
||||||
|
### Day 17: Chronospatial Computer
|
||||||
|
|
||||||
|
:mag_right: Puzzle: <https://adventofcode.com/2024/day/17>, :white_check_mark: Solver: [`ChronospatialComputer.cpp`](src/ChronospatialComputer.cpp)
|
||||||
|
|
||||||
|
For part 1, the solver implements the eight defined instructions and a runtime containing the program, an instruction pointer, the three registers, and an output buffer, which allows the execution of the given program. The solver framework had to be updated to allow non-integer solutions, i.e. strings.
|
||||||
|
|
||||||
|
For part 2, the algorithm appends iteratively 3-digit binary numbers in such a way, that the desired output is approached. The required search patterns and mappings were determined manually, and are predefined for the specific given program in code. It is not a generic solution.
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.
|
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.
|
||||||
|
@ -37,4 +37,5 @@ class ChronospatialComputer
|
|||||||
std::array<std::unique_ptr<ChronospatialComputerInstruction>, 8> instructions_;
|
std::array<std::unique_ptr<ChronospatialComputerInstruction>, 8> instructions_;
|
||||||
std::vector<int> program_;
|
std::vector<int> program_;
|
||||||
ChronospatialComputerState state_;
|
ChronospatialComputerState state_;
|
||||||
|
long long findQuineState();
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@ class ChronospatialComputerInstruction
|
|||||||
protected:
|
protected:
|
||||||
virtual void runValue(ChronospatialComputerState& state, const int operandValue) const = 0;
|
virtual void runValue(ChronospatialComputerState& state, const int operandValue) const = 0;
|
||||||
private:
|
private:
|
||||||
std::function<int(const std::array<int, 3>&, const int)> operandFunctor_;
|
std::function<int(const std::array<long long, 3>&, const int)> operandFunctor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma region ChronospatialComputerDivisionInstruction
|
#pragma region ChronospatialComputerDivisionInstruction
|
||||||
|
@ -22,6 +22,6 @@ class ChronospatialComputerState
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
size_t instructionPointer{ 0 };
|
size_t instructionPointer{ 0 };
|
||||||
std::array<int, 3> registers{};
|
std::array<long long, 3> registers{};
|
||||||
std::ostringstream output{};
|
std::ostringstream output{};
|
||||||
};
|
};
|
||||||
|
@ -73,6 +73,7 @@ void ChronospatialComputer::finish()
|
|||||||
{
|
{
|
||||||
runProgram(program_, state_);
|
runProgram(program_, state_);
|
||||||
part1 = state_.output.str();
|
part1 = state_.output.str();
|
||||||
|
part2 = findQuineState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChronospatialComputer::runProgram(const std::vector<int>& program, ChronospatialComputerState& state) const
|
void ChronospatialComputer::runProgram(const std::vector<int>& program, ChronospatialComputerState& state) const
|
||||||
@ -83,3 +84,80 @@ void ChronospatialComputer::runProgram(const std::vector<int>& program, Chronosp
|
|||||||
instructions_[program[state.instructionPointer]]->run(state, program[state.instructionPointer + 1]);
|
instructions_[program[state.instructionPointer]]->run(state, program[state.instructionPointer + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long long ChronospatialComputer::findQuineState()
|
||||||
|
{
|
||||||
|
// The following masks and output patterns are specific to a single program and have been precalculated manually.
|
||||||
|
// The given Chronospatial Computer program essentially does the following calculations.
|
||||||
|
//
|
||||||
|
// A' = A >> 3
|
||||||
|
// OUT = (((A mod 8) xor 4) xor (A shr ((A mod 8) xor 1))) mod 8,
|
||||||
|
//
|
||||||
|
// where A is the initial value of register A, A' is the value of register A when program flow reaches the jump
|
||||||
|
// instruction at the end of the program, and OUT is the single value written to the output when program flow reaches
|
||||||
|
// that same jump instruction.
|
||||||
|
//
|
||||||
|
// So given a desired output OUT, the number to be appended to the accumulated result can be found by matching the
|
||||||
|
// output patterns below against the masked trailing bits of the accumulated result, see the calculations below.
|
||||||
|
|
||||||
|
// Array index corresponds to number to append.
|
||||||
|
std::array<int, 8> masks{ 0b1, 0b0, 0b111, 0b11, 0b11100, 0b1110, 0b1110000, 0b111000 };
|
||||||
|
|
||||||
|
// Array index is the output number.
|
||||||
|
// First pair element is the number appended to register A.
|
||||||
|
// Second pair element is the pattern to match against register A.
|
||||||
|
std::array<std::vector<std::pair<int, int>>, 8> outputPatterns
|
||||||
|
{ {
|
||||||
|
// output 0
|
||||||
|
{ { 0, 0b1 }, { 2, 0b110 }, { 4, 0b00000 }, { 5, 0b0010 }, { 6, 0b0100000 }, { 7, 0b0110000 } },
|
||||||
|
// output 1
|
||||||
|
{ { 2, 0b111 }, { 3, 0b11 }, { 4, 0b00100 }, { 5, 0b0000 }, { 6, 0b0110000 }, { 7, 0b010000 } },
|
||||||
|
// output 2
|
||||||
|
{ { 2, 0b100 }, { 4, 0b01000 }, { 5, 0b0110 }, { 6, 0b0000000 }, { 7, 0b001000 } },
|
||||||
|
// output 3
|
||||||
|
{ { 2, 0b101 }, { 3, 0b10 }, { 4, 0b01100 }, { 5, 0b0100 }, { 6, 0b0010000 }, { 7, 0b000000 } },
|
||||||
|
// output 4
|
||||||
|
{ { 0, 0b0 }, { 1, 0b0 }, { 2, 0b010 }, { 4, 0b10000 }, { 5, 0b1010 }, { 6, 0b1100000 }, { 7, 0b111000 } },
|
||||||
|
// output 5
|
||||||
|
{ { 2, 0b011 }, { 3, 0b01 }, { 4, 0b10100 }, { 5, 0b1000 }, { 6, 0b1110000 }, { 7, 0b110000 } },
|
||||||
|
// output 6
|
||||||
|
{ { 2, 0b000 }, { 4, 0b11000 }, { 5, 0b1110 }, { 6, 0b1000000 }, { 7, 0b101000 } },
|
||||||
|
// output 7
|
||||||
|
{ { 2, 0b001 }, { 3, 0b00 }, { 4, 0b11100 }, { 5, 0b1100 }, { 6, 0b1010000 }, { 7, 0b100000 } }
|
||||||
|
} };
|
||||||
|
|
||||||
|
long long result{ 0 };
|
||||||
|
size_t i{ program_.size() };
|
||||||
|
int lastAppend{ -1 };
|
||||||
|
|
||||||
|
// Appends 3-digit binary numbers to the end of 'result' that will cause the program to output the next desired
|
||||||
|
// number (from 'program_'). The target output numbers are considered in reverse order.
|
||||||
|
while (i > 0)
|
||||||
|
{
|
||||||
|
auto patterns = outputPatterns[program_[i - 1]];
|
||||||
|
auto it = std::find_if(patterns.begin(), patterns.end(),
|
||||||
|
[&lastAppend, &result, &masks](auto& x) {
|
||||||
|
return lastAppend < x.first && (result & masks[x.first]) == x.second; });
|
||||||
|
if (it != patterns.end())
|
||||||
|
{
|
||||||
|
// Appends the next number 'it->first' that will result in the desired output 'program_[i - 1]'.
|
||||||
|
lastAppend = -1;
|
||||||
|
result = (result << 3) + it->first;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else if (i < program_.size())
|
||||||
|
{
|
||||||
|
// Backtrack because no 3-digit number can be appended to yield the desired output.
|
||||||
|
lastAppend = result & 0b111;
|
||||||
|
result >>= 3;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This should not execute, it's a safeguard against an infinite loop in case of unexpected input.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -22,10 +22,10 @@ ChronospatialComputerInstruction::ChronospatialComputerInstruction(const Chronos
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case ChronospatialComputerOperandType::Literal :
|
case ChronospatialComputerOperandType::Literal :
|
||||||
operandFunctor_ = [](const std::array<int, 3>& registers, const int operand) { return operand; };
|
operandFunctor_ = [](const std::array<long long, 3>& registers, const int operand) { return operand; };
|
||||||
break;
|
break;
|
||||||
case ChronospatialComputerOperandType::Combo :
|
case ChronospatialComputerOperandType::Combo :
|
||||||
operandFunctor_ = [](const std::array<int, 3>& registers, const int operand)
|
operandFunctor_ = [](const std::array<long long, 3>& registers, const int operand)
|
||||||
{ return operand < 4 ? operand : registers[static_cast<size_t>(operand - 4)]; };
|
{ return operand < 4 ? operand : registers[static_cast<size_t>(operand - 4)]; };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ class TestContext
|
|||||||
const long long expected2, const std::vector<std::string>& inputPaths);
|
const long long expected2, const std::vector<std::string>& inputPaths);
|
||||||
void runPart1(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
void runPart1(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
||||||
const std::vector<std::string>& inputPaths);
|
const std::vector<std::string>& inputPaths);
|
||||||
|
void runPart1(const std::unique_ptr<Solver<std::string, long long>>&& solver, const std::string expected,
|
||||||
|
const std::vector<std::string>& inputPaths);
|
||||||
void runPart2(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
void runPart2(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
||||||
const std::vector<std::string>& inputPaths);
|
const std::vector<std::string>& inputPaths);
|
||||||
std::vector<std::string> getInputPaths() const;
|
std::vector<std::string> getInputPaths() const;
|
||||||
|
@ -276,11 +276,11 @@ TEST_CASE("[ChronospatialComputerTests]")
|
|||||||
TestContext test;
|
TestContext test;
|
||||||
SECTION("FullData")
|
SECTION("FullData")
|
||||||
{
|
{
|
||||||
test.run(std::make_unique<ChronospatialComputer>(), "5,0,3,5,7,6,1,5,4", 0, test.getInputPaths());
|
test.run(std::make_unique<ChronospatialComputer>(), "5,0,3,5,7,6,1,5,4", 164516454365621, test.getInputPaths());
|
||||||
}
|
}
|
||||||
SECTION("ExampleData")
|
SECTION("ExampleData")
|
||||||
{
|
{
|
||||||
test.run(std::make_unique<ChronospatialComputer>(), "4,6,3,5,6,3,5,2,1,0", 0, test.getExampleInputPaths());
|
test.runPart1(std::make_unique<ChronospatialComputer>(), "4,6,3,5,6,3,5,2,1,0", test.getExampleInputPaths());
|
||||||
}
|
}
|
||||||
SECTION("ExampleInstruction1")
|
SECTION("ExampleInstruction1")
|
||||||
{
|
{
|
||||||
|
@ -58,6 +58,15 @@ void TestContext::runPart1(const std::unique_ptr<Solver<long long, long long>>&&
|
|||||||
REQUIRE(expected == solver->getResultPart1());
|
REQUIRE(expected == solver->getResultPart1());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestContext::runPart1(const std::unique_ptr<Solver<std::string, long long>>&& solver, const std::string expected,
|
||||||
|
const std::vector<std::string>& inputPaths)
|
||||||
|
{
|
||||||
|
SolverEngine solverEngine{ inputPaths };
|
||||||
|
solverEngine.run(*solver);
|
||||||
|
|
||||||
|
REQUIRE(expected == solver->getResultPart1());
|
||||||
|
}
|
||||||
|
|
||||||
void TestContext::runPart2(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
void TestContext::runPart2(const std::unique_ptr<Solver<long long, long long>>&& solver, const long long expected,
|
||||||
const std::vector<std::string>& inputPaths)
|
const std::vector<std::string>& inputPaths)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user