From e0cd31590939d68e758d1f28fa85101a88b62791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 19 Mar 2025 09:56:50 +0100 Subject: [PATCH] Add solution for "Day 6: Guard Gallivant", part 2 --- README.md | 8 ++ include/aoc/GuardGallivant.hpp | 25 ++++- include/aoc/Point2.hpp | 5 +- src/GuardGallivant.cpp | 167 +++++++++++++++++++++++++++------ src/Point2.cpp | 6 ++ tests/src/TestCases.cpp | 4 +- 6 files changed, 178 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index abff42e..cfb953f 100644 --- a/README.md +++ b/README.md @@ -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. +### Day 6: Guard Gallivant + +:mag_right: Puzzle: , :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 :mag_right: Puzzle: , :white_check_mark: Solver: [`BridgeRepair.cpp`](src/BridgeRepair.cpp) diff --git a/include/aoc/GuardGallivant.hpp b/include/aoc/GuardGallivant.hpp index 24f441b..35ca29a 100644 --- a/include/aoc/GuardGallivant.hpp +++ b/include/aoc/GuardGallivant.hpp @@ -1,5 +1,5 @@ // 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 // the terms of the GNU General Public License as published by the Free Software @@ -15,7 +15,12 @@ #pragma once +#include +#include + +#include #include +#include class GuardGallivant : public LinesSolver @@ -26,11 +31,23 @@ class GuardGallivant virtual void processDataLine(const std::string& line) override; virtual void finish() override; private: + typedef Grid VisitGrid; + typedef Grid> PathGrid; static constexpr size_t getStartDirectionIndex(); static constexpr char getStartChar(); - static constexpr char getVisitedChar(); static constexpr char getObstructionChar(); + static constexpr char getEmptyChar(); Point2 start_{}; - void visitPosition(const Point2& current); - size_t turnDirection(const size_t current) const; + void tracePath(); + 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; }; diff --git a/include/aoc/Point2.hpp b/include/aoc/Point2.hpp index 9bdade1..97d4556 100644 --- a/include/aoc/Point2.hpp +++ b/include/aoc/Point2.hpp @@ -1,5 +1,5 @@ // 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 // the terms of the GNU General Public License as published by the Free Software @@ -16,6 +16,7 @@ #pragma once #include +#include class Point2 { @@ -46,3 +47,5 @@ class Point2 Point2& operator*=(const int rhs); int& operator[](size_t coordinateIndex); }; + +std::ostream& operator<<(std::ostream& os, const Point2& rhs); diff --git a/src/GuardGallivant.cpp b/src/GuardGallivant.cpp index 6f7116a..4fdce50 100644 --- a/src/GuardGallivant.cpp +++ b/src/GuardGallivant.cpp @@ -27,33 +27,18 @@ const int GuardGallivant::getPuzzleDay() const 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) { - start_ = Point2{ static_cast(pos), static_cast(lines.size() - 1) }; + start_ = Point2{ static_cast(pos), static_cast(lines.size()) }; } + + LinesSolver::processDataLine(line); } void GuardGallivant::finish() { - auto dirIndex{ getStartDirectionIndex() }; - 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]; - } + tracePath(); } constexpr size_t GuardGallivant::getStartDirectionIndex() @@ -66,26 +51,148 @@ constexpr char GuardGallivant::getStartChar() return '^'; } -constexpr char GuardGallivant::getVisitedChar() -{ - return 'X'; -} - constexpr char GuardGallivant::getObstructionChar() { return '#'; } -void GuardGallivant::visitPosition(const Point2& current) +constexpr char GuardGallivant::getEmptyChar() { - if (getCharAt(current) != getVisitedChar()) + return '.'; +} + +void GuardGallivant::tracePath() +{ + 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)) { - setCharAt(current, getVisitedChar()); + 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++; } } -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(); } diff --git a/src/Point2.cpp b/src/Point2.cpp index 9469644..96bd023 100644 --- a/src/Point2.cpp +++ b/src/Point2.cpp @@ -107,3 +107,9 @@ int& Point2::operator[](size_t coordinateIndex) { return coordinateIndex == 0 ? x : y; } + +std::ostream& operator<<(std::ostream& os, const Point2& rhs) +{ + os << "(" << rhs.x << ", " << rhs.y << ")"; + return os; +} \ No newline at end of file diff --git a/tests/src/TestCases.cpp b/tests/src/TestCases.cpp index 915f01a..651b5bd 100644 --- a/tests/src/TestCases.cpp +++ b/tests/src/TestCases.cpp @@ -107,11 +107,11 @@ TEST_CASE("[GuardGallivantTests]") TestContext test; SECTION("FullData") { - test.run(std::make_unique(), 4665, 0, test.getInputPaths()); + test.run(std::make_unique(), 4665, 1688, test.getInputPaths()); } SECTION("ExampleData") { - test.run(std::make_unique(), 41, 0, test.getExampleInputPaths()); + test.run(std::make_unique(), 41, 6, test.getExampleInputPaths()); } }