From f8f431b61a0b7e2e4099608625a9a689fdf34b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sun, 25 May 2025 21:15:11 +0200 Subject: [PATCH] Add solution for "Day 18: RAM Run", part 2 --- README.md | 8 +++ include/aoc/RamRun.hpp | 5 +- include/aoc/common/WeightedEdgeGraph.hpp | 18 +++++- src/RamRun.cpp | 77 ++++++++++++++++++++---- src/ReindeerMaze.cpp | 6 +- src/common/WeightedEdgeGraph.cpp | 22 ++++--- tests/src/TestCases.cpp | 4 +- 7 files changed, 113 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 24bdbfd..5563322 100644 --- a/README.md +++ b/README.md @@ -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: , :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. diff --git a/include/aoc/RamRun.hpp b/include/aoc/RamRun.hpp index 1e8c74a..80df240 100644 --- a/include/aoc/RamRun.hpp +++ b/include/aoc/RamRun.hpp @@ -33,8 +33,11 @@ class RamRun int maxBytes_; int nBytes_; Grid 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(); }; diff --git a/include/aoc/common/WeightedEdgeGraph.hpp b/include/aoc/common/WeightedEdgeGraph.hpp index c53fc10..d998fa9 100644 --- a/include/aoc/common/WeightedEdgeGraph.hpp +++ b/include/aoc/common/WeightedEdgeGraph.hpp @@ -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 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 distances; + std::vector predecessors; + }; + + PathsResult dijkstra(const int source) const; + static constexpr int getInfiniteDistance(); struct Incidence { diff --git a/src/RamRun.cpp b/src/RamRun.cpp index 1e82513..84b2b83 100644 --- a/src/RamRun.cpp +++ b/src/RamRun.cpp @@ -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(); + } + } + } +} diff --git a/src/ReindeerMaze.cpp b/src/ReindeerMaze.cpp index 35b6b87..8c88bd3 100644 --- a/src/ReindeerMaze.cpp +++ b/src/ReindeerMaze.cpp @@ -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() diff --git a/src/common/WeightedEdgeGraph.cpp b/src/common/WeightedEdgeGraph.cpp index 9f29fe0..8230775 100644 --- a/src/common/WeightedEdgeGraph.cpp +++ b/src/common/WeightedEdgeGraph.cpp @@ -49,13 +49,13 @@ int WeightedEdgeGraph::getEdgeWeight(const int edge) const return edgeWeights_[edge]; } -std::vector WeightedEdgeGraph::dijkstra(const int source) const +WeightedEdgeGraph::PathsResult WeightedEdgeGraph::dijkstra(const int source) const { - std::vector distances(firstVertexIncidences_.size(), std::numeric_limits::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, decltype(compare)> queue{ compare }; - distances[source] = 0; + result.distances[source] = 0; queue.push(source); while (!queue.empty()) @@ -65,16 +65,22 @@ std::vector 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::max(); } WeightedEdgeGraph::NeighborIterator WeightedEdgeGraph::begin(const int vertex) const diff --git a/tests/src/TestCases.cpp b/tests/src/TestCases.cpp index 13d2c63..81c1d4f 100644 --- a/tests/src/TestCases.cpp +++ b/tests/src/TestCases.cpp @@ -332,11 +332,11 @@ TEST_CASE("[RamRunTests]") TestContext test; SECTION("FullData") { - test.run(std::make_unique(), 248, "", test.getInputPaths()); + test.run(std::make_unique(), 248, "32,55", test.getInputPaths()); } SECTION("ExampleData") { - test.run(std::make_unique(7, 12), 22, "", test.getExampleInputPaths()); + test.run(std::make_unique(7, 12), 22, "6,1", test.getExampleInputPaths()); } }