Add solution for "Day 18: RAM Run", part 2

This commit is contained in:
Stefan Müller 2025-05-25 21:15:11 +02:00
parent bb1dab33e9
commit f8f431b61a
7 changed files with 113 additions and 27 deletions

View File

@ -132,6 +132,14 @@ For part 1, the solver implements the eight defined instructions and a runtime c
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. 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.
### Day 18: RAM Run
:mag_right: Puzzle: <https://adventofcode.com/2024/day/18>, :white_check_mark: Solver: [`RamRun.cpp`](src/RamRun.cpp)
After the solver tracked the first 1024 corrupted memory positions, it constructs a graph of all remaining positions and their adjacencies, and runs Dijkstra's algorithm I already used for [day 16](#day-16-reindeer-maze) to calculate the length of the shortest path to the exit.
Conveniently, Dijkstra's algorithm also finds the shortest path itself, so after each additional falling byte, the algorithm checks whether it hit the current shortest path and if so, tries to find a new one to the exit. The solution for part two is found once this there is no path any more.
## 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.

View File

@ -33,8 +33,11 @@ class RamRun
int maxBytes_; int maxBytes_;
int nBytes_; int nBytes_;
Grid<int> vertexReferences_; Grid<int> vertexReferences_;
WeightedEdgeGraph::PathsResult dijkstraResult_;
static constexpr char getUnknownVertexReference(); static constexpr char getUnknownVertexReference();
static constexpr char getNoVertexReference(); static constexpr char getNoVertexReference();
void buildGraph(WeightedEdgeGraph& graph); bool tryMarkCorrupted(const std::string& line, int& corruptedVertex);
WeightedEdgeGraph buildGraph();
int getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y); int getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y);
void resetVertexReferences();
}; };

View File

@ -28,7 +28,23 @@ class WeightedEdgeGraph
void addEdge(const int vertex1, const int vertex2, const int weight); void addEdge(const int vertex1, const int vertex2, const int weight);
int getVertexWeight(const int vertex) const; int getVertexWeight(const int vertex) const;
int getEdgeWeight(const int edge) const; int getEdgeWeight(const int edge) const;
std::vector<int> dijkstra(const int source) const;
struct PathsResult
{
PathsResult()
: distances{}, predecessors{}
{
}
PathsResult(const size_t size, const int initialDistance, const int initialPredecessor)
: distances(size, initialDistance), predecessors(size, initialPredecessor)
{
}
std::vector<int> distances;
std::vector<int> predecessors;
};
PathsResult dijkstra(const int source) const;
static constexpr int getInfiniteDistance();
struct Incidence struct Incidence
{ {

View File

@ -35,22 +35,42 @@ const int RamRun::getPuzzleDay() const
void RamRun::processDataLine(const std::string& line) void RamRun::processDataLine(const std::string& line)
{ {
if (maxBytes_ > nBytes_++) if (part2 == "")
{ {
std::istringstream stream{ line }; int corrupted{ -1 };
Point2 position; if (!tryMarkCorrupted(line, corrupted))
char c;
if (stream >> position.x >> c >> position.y)
{ {
vertexReferences_.cell(position) = getNoVertexReference(); return;
} }
if (maxBytes_ == nBytes_) if (maxBytes_ == ++nBytes_)
{ {
WeightedEdgeGraph graph{}; // Calculates initial distances.
buildGraph(graph); auto graph = buildGraph();
auto distances = graph.dijkstra(vertexReferences_[0][0]); dijkstraResult_ = graph.dijkstra(vertexReferences_[0][0]);
part1 = distances[vertexReferences_[memorySize_ - 1][memorySize_ - 1]]; part1 = dijkstraResult_.distances[vertexReferences_[memorySize_ - 1][memorySize_ - 1]];
}
else if (maxBytes_ < nBytes_)
{
int pathVertex = vertexReferences_[memorySize_ - 1][memorySize_ - 1];
while (pathVertex >= 0 && pathVertex != corrupted)
{
pathVertex = dijkstraResult_.predecessors[pathVertex];
}
if (pathVertex == corrupted)
{
// Calculates new paths and distances after path was interruped by corrupted position, and checks if path
// is now blocked.
resetVertexReferences();
auto graph = buildGraph();
dijkstraResult_ = graph.dijkstra(vertexReferences_[0][0]);
if (dijkstraResult_.distances[vertexReferences_[memorySize_ - 1][memorySize_ - 1]]
== WeightedEdgeGraph::getInfiniteDistance())
{
// Path is blocked.
part2 = line;
}
}
} }
} }
} }
@ -69,8 +89,25 @@ constexpr char RamRun::getNoVertexReference()
return -2; return -2;
} }
void RamRun::buildGraph(WeightedEdgeGraph& graph) // Marks the position extracted from the 'line' as corrupted in the vertex reference grid.
bool RamRun::tryMarkCorrupted(const std::string& line, int& corruptedVertex)
{ {
std::istringstream stream{ line };
Point2 position;
char c;
if (stream >> position.x >> c >> position.y)
{
corruptedVertex = vertexReferences_.cell(position);
vertexReferences_.cell(position) = getNoVertexReference();
return true;
}
corruptedVertex = -1;
return false;
}
WeightedEdgeGraph RamRun::buildGraph()
{
WeightedEdgeGraph graph;
for (size_t j = 0; j < vertexReferences_.getNRows(); j++) for (size_t j = 0; j < vertexReferences_.getNRows(); j++)
{ {
for (size_t i = 0; i < vertexReferences_.getNColumns(); i++) for (size_t i = 0; i < vertexReferences_.getNColumns(); i++)
@ -95,6 +132,8 @@ void RamRun::buildGraph(WeightedEdgeGraph& graph)
} }
} }
} }
return graph;
} }
int RamRun::getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y) int RamRun::getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y)
@ -105,3 +144,17 @@ int RamRun::getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y)
} }
return vertexReferences_[y][x]; return vertexReferences_[y][x];
} }
void RamRun::resetVertexReferences()
{
for (size_t j = 0; j < vertexReferences_.getNRows(); j++)
{
for (size_t i = 0; i < vertexReferences_.getNColumns(); i++)
{
if (vertexReferences_[j][i] != getNoVertexReference())
{
vertexReferences_[j][i] = getUnknownVertexReference();
}
}
}
}

View File

@ -45,9 +45,9 @@ void ReindeerMaze::finish()
auto exit = graph.addVertex(0); auto exit = graph.addVertex(0);
buildPathSegmentGraph(graph, vertexAttachedPositions, entry, exit); buildPathSegmentGraph(graph, vertexAttachedPositions, entry, exit);
auto shortestDistances = graph.dijkstra(exit); auto result = graph.dijkstra(exit);
part1 = shortestDistances[entry] / 2; part1 = result.distances[entry] / 2;
part2 = calcShortestPaths(graph, vertexAttachedPositions, entry, exit, shortestDistances); part2 = calcShortestPaths(graph, vertexAttachedPositions, entry, exit, result.distances);
} }
constexpr char ReindeerMaze::getStartChar() constexpr char ReindeerMaze::getStartChar()

View File

@ -49,13 +49,13 @@ int WeightedEdgeGraph::getEdgeWeight(const int edge) const
return edgeWeights_[edge]; return edgeWeights_[edge];
} }
std::vector<int> WeightedEdgeGraph::dijkstra(const int source) const WeightedEdgeGraph::PathsResult WeightedEdgeGraph::dijkstra(const int source) const
{ {
std::vector<int> distances(firstVertexIncidences_.size(), std::numeric_limits<int>::max()); PathsResult result(firstVertexIncidences_.size(), getInfiniteDistance(), -1);
auto compare = [&distances](int left, int right) { return distances[left] > distances[right]; }; auto compare = [&result](int left, int right) { return result.distances[left] > result.distances[right]; };
std::priority_queue<int, std::vector<int>, decltype(compare)> queue{ compare }; std::priority_queue<int, std::vector<int>, decltype(compare)> queue{ compare };
distances[source] = 0; result.distances[source] = 0;
queue.push(source); queue.push(source);
while (!queue.empty()) while (!queue.empty())
@ -65,16 +65,22 @@ std::vector<int> WeightedEdgeGraph::dijkstra(const int source) const
for (auto neighbor = begin(v); neighbor != end(); ++neighbor) for (auto neighbor = begin(v); neighbor != end(); ++neighbor)
{ {
int newDistance{ distances[v] + edgeWeights_[neighbor->edge] }; int newDistance{ result.distances[v] + edgeWeights_[neighbor->edge] };
if (distances[neighbor->vertex] > newDistance) if (result.distances[neighbor->vertex] > newDistance)
{ {
distances[neighbor->vertex] = newDistance; result.distances[neighbor->vertex] = newDistance;
result.predecessors[neighbor->vertex] = v;
queue.push(neighbor->vertex); queue.push(neighbor->vertex);
} }
} }
} }
return distances; return result;
}
constexpr int WeightedEdgeGraph::getInfiniteDistance()
{
return std::numeric_limits<int>::max();
} }
WeightedEdgeGraph::NeighborIterator WeightedEdgeGraph::begin(const int vertex) const WeightedEdgeGraph::NeighborIterator WeightedEdgeGraph::begin(const int vertex) const

View File

@ -332,11 +332,11 @@ TEST_CASE("[RamRunTests]")
TestContext test; TestContext test;
SECTION("FullData") SECTION("FullData")
{ {
test.run(std::make_unique<RamRun>(), 248, "", test.getInputPaths()); test.run(std::make_unique<RamRun>(), 248, "32,55", test.getInputPaths());
} }
SECTION("ExampleData") SECTION("ExampleData")
{ {
test.run(std::make_unique<RamRun>(7, 12), 22, "", test.getExampleInputPaths()); test.run(std::make_unique<RamRun>(7, 12), 22, "6,1", test.getExampleInputPaths());
} }
} }