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

@ -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.
### 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
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.

@ -33,8 +33,11 @@ class RamRun
int maxBytes_;
int nBytes_;
Grid<int> vertexReferences_;
WeightedEdgeGraph::PathsResult dijkstraResult_;
static constexpr char getUnknownVertexReference();
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);
void resetVertexReferences();
};

@ -28,7 +28,23 @@ class WeightedEdgeGraph
void addEdge(const int vertex1, const int vertex2, const int weight);
int getVertexWeight(const int vertex) 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
{

@ -35,22 +35,42 @@ const int RamRun::getPuzzleDay() const
void RamRun::processDataLine(const std::string& line)
{
if (maxBytes_ > nBytes_++)
if (part2 == "")
{
std::istringstream stream{ line };
Point2 position;
char c;
if (stream >> position.x >> c >> position.y)
int corrupted{ -1 };
if (!tryMarkCorrupted(line, corrupted))
{
vertexReferences_.cell(position) = getNoVertexReference();
return;
}
if (maxBytes_ == nBytes_)
if (maxBytes_ == ++nBytes_)
{
WeightedEdgeGraph graph{};
buildGraph(graph);
auto distances = graph.dijkstra(vertexReferences_[0][0]);
part1 = distances[vertexReferences_[memorySize_ - 1][memorySize_ - 1]];
// Calculates initial distances.
auto graph = buildGraph();
dijkstraResult_ = graph.dijkstra(vertexReferences_[0][0]);
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;
}
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 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)
@ -105,3 +144,17 @@ int RamRun::getVertex(WeightedEdgeGraph& graph, const size_t x, const size_t y)
}
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();
}
}
}
}

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

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

@ -332,11 +332,11 @@ TEST_CASE("[RamRunTests]")
TestContext test;
SECTION("FullData")
{
test.run(std::make_unique<RamRun>(), 248, "", test.getInputPaths());
test.run(std::make_unique<RamRun>(), 248, "32,55", test.getInputPaths());
}
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());
}
}