Add solution for "Day 6: Guard Gallivant", part 2

This commit is contained in:
Stefan Müller 2025-03-19 09:56:50 +01:00
parent 51b537098d
commit e0cd315909
6 changed files with 178 additions and 37 deletions

View File

@ -44,6 +44,14 @@ For this puzzle I added a class for [points in two-dimensional space](include/ao
My implementation uses an ordering matrix (a two-dimensional boolean array) to track which page combinations are ordered, and then queries that matrix for each ordered combination of pages in a single line. The same matrix can then also be used for a custom sort function for part 2. My implementation uses an ordering matrix (a two-dimensional boolean array) to track which page combinations are ordered, and then queries that matrix for each ordered combination of pages in a single line. The same matrix can then also be used for a custom sort function for part 2.
### Day 6: Guard Gallivant
:mag_right: Puzzle: <https://adventofcode.com/2024/day/6>, :white_check_mark: Solver: [`GuardGallivant.cpp`](src/GuardGallivant.cpp)
The solver for this puzzle simply simulates the guard traversing the map, turning them right at obstacles and counting the unique positions the guard will visit for part one. At each newly visited position, the algorithm will place a temporary obstacle at the next empty position and check if that obstacle will create a cycle for part two.
In order to improve performance for part two, the solver backtraces at turns to find all steps on the map that would lead to the position the guard is currently in, and uses this data to find cycles early.
### Day 7: Bridge Repair ### Day 7: Bridge Repair
:mag_right: Puzzle: <https://adventofcode.com/2024/day/7>, :white_check_mark: Solver: [`BridgeRepair.cpp`](src/BridgeRepair.cpp) :mag_right: Puzzle: <https://adventofcode.com/2024/day/7>, :white_check_mark: Solver: [`BridgeRepair.cpp`](src/BridgeRepair.cpp)

View File

@ -1,5 +1,5 @@
// Solutions to the Advent Of Code 2024. // Solutions to the Advent Of Code 2024.
// Copyright (C) 2024 Stefan Müller // Copyright (C) 2024-2025 Stefan Müller
// //
// This program is free software: you can redistribute it and/or modify it under // This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software // the terms of the GNU General Public License as published by the Free Software
@ -15,7 +15,12 @@
#pragma once #pragma once
#include <bitset>
#include <vector>
#include <aoc/Grid.hpp>
#include <aoc/LinesSolver.hpp> #include <aoc/LinesSolver.hpp>
#include <aoc/Point2.hpp>
class GuardGallivant class GuardGallivant
: public LinesSolver : public LinesSolver
@ -26,11 +31,23 @@ class GuardGallivant
virtual void processDataLine(const std::string& line) override; virtual void processDataLine(const std::string& line) override;
virtual void finish() override; virtual void finish() override;
private: private:
typedef Grid<bool> VisitGrid;
typedef Grid<std::bitset<4>> PathGrid;
static constexpr size_t getStartDirectionIndex(); static constexpr size_t getStartDirectionIndex();
static constexpr char getStartChar(); static constexpr char getStartChar();
static constexpr char getVisitedChar();
static constexpr char getObstructionChar(); static constexpr char getObstructionChar();
static constexpr char getEmptyChar();
Point2 start_{}; Point2 start_{};
void visitPosition(const Point2& current); void tracePath();
size_t turnDirection(const size_t current) const; bool tracePathCycleTest(const Point2& startPosition, const size_t startDirectionIndex, PathGrid& mainPathGrid,
PathGrid& cyclePathGrid);
bool checkTurn(const Point2& current, const Point2& next, size_t& directionIndex, PathGrid& pathGrid);
void visitPosition(const Point2& current, const size_t directionIndex, VisitGrid& visitGrid);
void tracePosition(const Point2& current, const size_t directionIndex, PathGrid& pathGrid);
void backtracePath(const Point2& current, const size_t directionIndex, PathGrid& pathGrid);
void backtraceTurn(const Point2& current, const size_t reverseTurnDirectionIndex, PathGrid& pathGrid);
size_t turnDirectionRight(const size_t currentDirectionIndex) const;
size_t turnDirectionLeft(const size_t currentDirectionIndex) const;
size_t revertDirection(const size_t currentDirectionIndex) const;
bool isObstruction(const Point2& position) const;
}; };

View File

@ -1,5 +1,5 @@
// Solutions to the Advent Of Code 2024. // Solutions to the Advent Of Code 2024.
// Copyright (C) 2024 Stefan Müller // Copyright (C) 2024-2025 Stefan Müller
// //
// This program is free software: you can redistribute it and/or modify it under // This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software // the terms of the GNU General Public License as published by the Free Software
@ -16,6 +16,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <iostream>
class Point2 class Point2
{ {
@ -46,3 +47,5 @@ class Point2
Point2& operator*=(const int rhs); Point2& operator*=(const int rhs);
int& operator[](size_t coordinateIndex); int& operator[](size_t coordinateIndex);
}; };
std::ostream& operator<<(std::ostream& os, const Point2& rhs);

View File

@ -27,33 +27,18 @@ const int GuardGallivant::getPuzzleDay() const
void GuardGallivant::processDataLine(const std::string& line) void GuardGallivant::processDataLine(const std::string& line)
{ {
LinesSolver::processDataLine(line); auto pos = line.find(getStartChar());
auto pos{ line.find(getStartChar()) };
if (pos != std::string::npos) if (pos != std::string::npos)
{ {
start_ = Point2{ static_cast<int>(pos), static_cast<int>(lines.size() - 1) }; start_ = Point2{ static_cast<int>(pos), static_cast<int>(lines.size()) };
} }
LinesSolver::processDataLine(line);
} }
void GuardGallivant::finish() void GuardGallivant::finish()
{ {
auto dirIndex{ getStartDirectionIndex() }; tracePath();
auto current{ start_ };
visitPosition(current);
auto next{ current + Point2::cardinalDirections[dirIndex] };
while (isInBounds(next))
{
if (getCharAt(next) == getObstructionChar())
{
dirIndex = turnDirection(dirIndex);
}
else
{
current = next;
visitPosition(current);
}
next = current + Point2::cardinalDirections[dirIndex];
}
} }
constexpr size_t GuardGallivant::getStartDirectionIndex() constexpr size_t GuardGallivant::getStartDirectionIndex()
@ -66,26 +51,148 @@ constexpr char GuardGallivant::getStartChar()
return '^'; return '^';
} }
constexpr char GuardGallivant::getVisitedChar()
{
return 'X';
}
constexpr char GuardGallivant::getObstructionChar() constexpr char GuardGallivant::getObstructionChar()
{ {
return '#'; return '#';
} }
void GuardGallivant::visitPosition(const Point2& current) constexpr char GuardGallivant::getEmptyChar()
{ {
if (getCharAt(current) != getVisitedChar()) return '.';
}
void GuardGallivant::tracePath()
{ {
setCharAt(current, getVisitedChar()); VisitGrid visitGrid{ lines.size(), lines[0].size() };
visitGrid.fill(false);
PathGrid pathGrid{ lines.size(), lines[0].size() };
pathGrid.fill(0);
// PathGrid for cycle tests, reused to avoid repeated memory allocation.
PathGrid cyclePathGrid{ lines.size(), lines[0].size() };
size_t directionIndex{ getStartDirectionIndex() };
Point2 current{ start_ };
visitPosition(current, directionIndex, visitGrid);
tracePosition(current, directionIndex, pathGrid);
backtracePath(current, directionIndex, pathGrid);
auto next = current + Point2::cardinalDirections[directionIndex];
while (isInBounds(next))
{
if (!checkTurn(current, next, directionIndex, pathGrid))
{
if (!visitGrid.cell(next))
{
if (pathGrid.cell(current)[turnDirectionRight(directionIndex)])
{
part2++;
}
else
{
setCharAt(next, getObstructionChar());
if (tracePathCycleTest(current, turnDirectionRight(directionIndex), pathGrid, cyclePathGrid))
{
part2++;
}
setCharAt(next, getEmptyChar());
}
}
current = next;
visitPosition(current, directionIndex, visitGrid);
tracePosition(current, directionIndex, pathGrid);
}
next = current + Point2::cardinalDirections[directionIndex];
}
}
bool GuardGallivant::tracePathCycleTest(const Point2& startPosition, const size_t startDirectionIndex,
PathGrid& mainPathGrid, PathGrid& cyclePathGrid)
{
cyclePathGrid.fill(0);
size_t directionIndex{ startDirectionIndex };
Point2 current{ startPosition };
tracePosition(current, directionIndex, cyclePathGrid);
backtracePath(current, directionIndex, cyclePathGrid);
auto next = current + Point2::cardinalDirections[directionIndex];
while (isInBounds(next))
{
if (!checkTurn(current, next, directionIndex, cyclePathGrid))
{
if (mainPathGrid.cell(next)[directionIndex] || cyclePathGrid.cell(next)[directionIndex])
{
return true;
}
current = next;
tracePosition(current, directionIndex, cyclePathGrid);
}
next = current + Point2::cardinalDirections[directionIndex];
}
return false;
}
bool GuardGallivant::checkTurn(const Point2& current, const Point2& next, size_t& directionIndex, PathGrid& pathGrid)
{
if (getCharAt(next) == getObstructionChar())
{
directionIndex = turnDirectionRight(directionIndex);
backtracePath(current, directionIndex, pathGrid);
return true;
}
return false;
}
void GuardGallivant::visitPosition(const Point2& current, const size_t directionIndex, VisitGrid& visitGrid)
{
if (!visitGrid.cell(current))
{
visitGrid.cell(current) = true;
part1++; part1++;
} }
} }
size_t GuardGallivant::turnDirection(const size_t current) const void GuardGallivant::tracePosition(const Point2& current, const size_t directionIndex, PathGrid& pathGrid)
{ {
return current == 0 ? 3 : current - 1; pathGrid.cell(current)[directionIndex] = true;
backtraceTurn(current, turnDirectionLeft(directionIndex), pathGrid);
}
void GuardGallivant::backtracePath(const Point2& current, const size_t directionIndex, PathGrid& pathGrid)
{
auto oppositeDirectionIndex = revertDirection(directionIndex);
auto next = current + Point2::cardinalDirections[oppositeDirectionIndex];
while (isObstruction(next) && !pathGrid.cell(next)[directionIndex])
{
tracePosition(next, oppositeDirectionIndex, pathGrid);
next = next + Point2::cardinalDirections[oppositeDirectionIndex];
}
}
void GuardGallivant::backtraceTurn(const Point2& current, const size_t reverseTurnDirectionIndex, PathGrid& pathGrid)
{
auto left = current + Point2::cardinalDirections[reverseTurnDirectionIndex];
if (isObstruction(left))
{
backtracePath(current, reverseTurnDirectionIndex, pathGrid);
}
}
size_t GuardGallivant::turnDirectionRight(const size_t currentDirectionIndex) const
{
return currentDirectionIndex == 0 ? 3 : currentDirectionIndex - 1;
}
size_t GuardGallivant::turnDirectionLeft(const size_t currentDirectionIndex) const
{
return currentDirectionIndex == 3 ? 0 : currentDirectionIndex + 1;
}
size_t GuardGallivant::revertDirection(const size_t currentDirectionIndex) const
{
return currentDirectionIndex > 1 ? currentDirectionIndex - 2 : currentDirectionIndex + 2;
}
bool GuardGallivant::isObstruction(const Point2& position) const
{
return isInBounds(position) && getCharAt(position) == getObstructionChar();
} }

View File

@ -107,3 +107,9 @@ int& Point2::operator[](size_t coordinateIndex)
{ {
return coordinateIndex == 0 ? x : y; return coordinateIndex == 0 ? x : y;
} }
std::ostream& operator<<(std::ostream& os, const Point2& rhs)
{
os << "(" << rhs.x << ", " << rhs.y << ")";
return os;
}

View File

@ -107,11 +107,11 @@ TEST_CASE("[GuardGallivantTests]")
TestContext test; TestContext test;
SECTION("FullData") SECTION("FullData")
{ {
test.run(std::make_unique<GuardGallivant>(), 4665, 0, test.getInputPaths()); test.run(std::make_unique<GuardGallivant>(), 4665, 1688, test.getInputPaths());
} }
SECTION("ExampleData") SECTION("ExampleData")
{ {
test.run(std::make_unique<GuardGallivant>(), 41, 0, test.getExampleInputPaths()); test.run(std::make_unique<GuardGallivant>(), 41, 6, test.getExampleInputPaths());
} }
} }