Add solution for "Day 16: Reindeer Maze", part 2
This commit is contained in:
@@ -13,10 +13,12 @@
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <numeric>
|
||||
#include <stack>
|
||||
|
||||
#include <aoc/ReindeerMaze.hpp>
|
||||
|
||||
ReindeerMaze::ReindeerMaze(const int inputFileNameSuffix)
|
||||
: LinesSolver{ inputFileNameSuffix }
|
||||
ReindeerMaze::ReindeerMaze(const int inputFileNameSuffix) : LinesSolver{ inputFileNameSuffix }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,12 +34,19 @@ const int ReindeerMaze::getPuzzleDay() const
|
||||
|
||||
void ReindeerMaze::finish()
|
||||
{
|
||||
WeightedEdgeGraph graph{};
|
||||
auto entry = graph.addVertex();
|
||||
auto exit = graph.addVertex();
|
||||
buildPathSegmentGraph(graph, entry, exit);
|
||||
// Maps vertex index of the resulting graph to a pair of ids of the connected positions in the original maze. This
|
||||
// could technically be a vector, since the graph will return incremental non-negative integer vertex ids, but we
|
||||
// do not want to hardcode this.
|
||||
VertexAttachedPositions vertexAttachedPositions;
|
||||
|
||||
part1 = graph.dijkstra(entry, exit) / 2;
|
||||
WeightedEdgeGraph graph{};
|
||||
auto entry = graph.addVertex(0);
|
||||
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);
|
||||
}
|
||||
|
||||
constexpr char ReindeerMaze::getStartChar()
|
||||
@@ -62,7 +71,8 @@ constexpr int ReindeerMaze::getTurnCost()
|
||||
|
||||
// Constructs the graph of path segment incidences, starting with a graph that already contains the entry and the exit
|
||||
// vertices.
|
||||
void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int entry, const int exit)
|
||||
void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, VertexAttachedPositions& vertexAttachedPositions,
|
||||
const int entry, const int exit)
|
||||
{
|
||||
// Uses list for work items to prevent invalidation of iterators on add.
|
||||
std::list<ReindeerMazeCrossing> crossings{};
|
||||
@@ -79,12 +89,13 @@ void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int ent
|
||||
Point2 position{ startPosition + direction };
|
||||
|
||||
int pathCost{ 1 };
|
||||
int nTiles{ 0 };
|
||||
bool isSkipped{ false };
|
||||
|
||||
int nNext{ 0 };
|
||||
while (getCharAt(position) != getEndChar() &&
|
||||
std::find_if(crossings.begin(), crossings.end(),
|
||||
[position](auto& x) { return x.getPosition() == position; }) == crossings.end())
|
||||
std::find_if(crossings.begin(), crossings.end(),
|
||||
[position](auto& x) { return x.getPosition() == position; }) == crossings.end())
|
||||
{
|
||||
nNext = 0;
|
||||
Point2 nextPosition, nextDirection;
|
||||
@@ -124,6 +135,7 @@ void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int ent
|
||||
{
|
||||
position = nextPosition;
|
||||
pathCost++;
|
||||
nTiles++;
|
||||
if (direction != nextDirection)
|
||||
{
|
||||
pathCost += getTurnCost();
|
||||
@@ -161,7 +173,8 @@ void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int ent
|
||||
if (!isSkipped)
|
||||
{
|
||||
// Adds the new maze path segment as a vertex.
|
||||
auto pathVertex = graph.addVertex();
|
||||
auto pathVertex = graph.addVertex(nTiles);
|
||||
vertexAttachedPositions.emplace(pathVertex, makePositionsIdPair(startPosition, position));
|
||||
|
||||
// Updates start crossing.
|
||||
incidence->setPathVertex(pathVertex);
|
||||
@@ -170,9 +183,10 @@ void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int ent
|
||||
// Determines the end crossing of the new path segment and its incidence.
|
||||
std::list<ReindeerMazeCrossing>::iterator endCrossing;
|
||||
std::vector<ReindeerMazePathIncidence>::iterator endIncidence;
|
||||
endCrossing = nNext == 1 ? endCrossing = std::find_if(crossings.begin(), crossings.end(),
|
||||
[position](auto& x) { return x.getPosition() == position; })
|
||||
: --crossings.end();
|
||||
endCrossing = nNext == 1
|
||||
? endCrossing = std::find_if(crossings.begin(), crossings.end(),
|
||||
[position](auto& x) { return x.getPosition() == position; })
|
||||
: --crossings.end();
|
||||
if (endCrossing != crossings.end())
|
||||
{
|
||||
// This incidence must exist, no need to check the incidence iterator.
|
||||
@@ -183,10 +197,10 @@ void ReindeerMaze::buildPathSegmentGraph(WeightedEdgeGraph& graph, const int ent
|
||||
}
|
||||
|
||||
// Connects the new path segment to all adjacent path segments.
|
||||
AddPathSegmentEdges(graph, *incidence, startCrossing->incidences);
|
||||
addPathSegmentEdges(graph, *incidence, startCrossing->incidences);
|
||||
if (endCrossing != crossings.end())
|
||||
{
|
||||
AddPathSegmentEdges(graph, *endIncidence, endCrossing->incidences);
|
||||
addPathSegmentEdges(graph, *endIncidence, endCrossing->incidences);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -243,7 +257,7 @@ Point2 ReindeerMaze::findStart() const
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
void ReindeerMaze::AddPathSegmentEdges(WeightedEdgeGraph& graph, const ReindeerMazePathIncidence& pathIncidence,
|
||||
void ReindeerMaze::addPathSegmentEdges(WeightedEdgeGraph& graph, const ReindeerMazePathIncidence& pathIncidence,
|
||||
const std::vector<ReindeerMazePathIncidence>& otherPathIncidences)
|
||||
{
|
||||
for (auto& otherIncidence : otherPathIncidences)
|
||||
@@ -260,3 +274,87 @@ void ReindeerMaze::AddPathSegmentEdges(WeightedEdgeGraph& graph, const ReindeerM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, int> ReindeerMaze::makePositionsIdPair(const Point2& position1, const Point2& position2)
|
||||
{
|
||||
int offset{ static_cast<int>(lines.size()) };
|
||||
return std::make_pair(position1.x * offset + position1.y, position2.x * offset + position2.y);
|
||||
}
|
||||
|
||||
int ReindeerMaze::calcShortestPaths(const WeightedEdgeGraph& graph,
|
||||
const VertexAttachedPositions& vertexAttachedPositions, const int entry, const int exit,
|
||||
const std::vector<int>& shortestDistances)
|
||||
{
|
||||
std::set<int> shortestPathsVertices{};
|
||||
|
||||
std::stack<WeightedEdgeGraph::NeighborIterator> stack{};
|
||||
auto v = graph.begin(entry);
|
||||
stack.emplace(v);
|
||||
int cost{ 0 };
|
||||
|
||||
std::set<int> stackedVertices{};
|
||||
stackedVertices.insert(entry);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto& current = stack.top();
|
||||
int newCost{ 0 };
|
||||
while (current != graph.end() &&
|
||||
((newCost = graph.getEdgeWeight(current->edge) + cost) + shortestDistances[current->vertex] >
|
||||
shortestDistances[entry] ||
|
||||
stackedVertices.contains(current->vertex)))
|
||||
{
|
||||
current++;
|
||||
}
|
||||
bool isPathEnd{ current == graph.end() };
|
||||
|
||||
if (!isPathEnd)
|
||||
{
|
||||
if (current->vertex == exit)
|
||||
{
|
||||
// Adds this discovered shortest path to the result vertex set, and ends the path, since no other
|
||||
// neighbor of the previous vertex can also lead to a shortest path with the same path stack. The last
|
||||
// edge will be popped below.
|
||||
shortestPathsVertices.insert(stackedVertices.begin(), stackedVertices.end());
|
||||
isPathEnd = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Adds the next valid neighbor vertex to the current path on the stack.
|
||||
stackedVertices.insert(current->vertex);
|
||||
stack.emplace(graph.begin(current->vertex));
|
||||
cost = newCost;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPathEnd)
|
||||
{
|
||||
// Backtracks the last edge, since the path cannot continue from it.
|
||||
stack.pop();
|
||||
if (!stack.empty())
|
||||
{
|
||||
stackedVertices.erase(stack.top()->vertex);
|
||||
cost -= graph.getEdgeWeight(stack.top()->edge);
|
||||
stack.top()++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erases entry vertex because it has no attached vertices, and no weight.
|
||||
shortestPathsVertices.erase(entry);
|
||||
return std::accumulate(shortestPathsVertices.begin(), shortestPathsVertices.end(),
|
||||
getNUniqueAttachedPositions(vertexAttachedPositions, shortestPathsVertices),
|
||||
[&graph](int total, int x) { return total + graph.getVertexWeight(x); });
|
||||
}
|
||||
|
||||
int ReindeerMaze::getNUniqueAttachedPositions(const VertexAttachedPositions& vertexAttachedPositions,
|
||||
const std::set<int>& vertices)
|
||||
{
|
||||
std::set<int> positions{};
|
||||
for (const int x : vertices)
|
||||
{
|
||||
positions.insert(vertexAttachedPositions.at(x).first);
|
||||
positions.insert(vertexAttachedPositions.at(x).second);
|
||||
}
|
||||
return static_cast<int>(positions.size());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user