Add solution for "Day 14: Restroom Redoubt", part 2

This commit is contained in:
2025-02-25 20:16:03 +01:00
parent a75535bab7
commit 8d75bdfbef
8 changed files with 128 additions and 7 deletions

View File

@@ -15,6 +15,8 @@
#include <aoc/Math.hpp>
#include <array>
int Math::ipow(const int base, const int exponent)
{
int result = 1;
@@ -24,3 +26,22 @@ int Math::ipow(const int base, const int exponent)
}
return result;
}
std::tuple<int, int, int> Math::extendedEuclid(const int a, const int b)
{
std::array<std::pair<int, int>, 3> rst{ { { a, b }, { 1, 0 }, { 0, 1 } } };
int q;
int r;
while (rst[0].second > 0)
{
q = rst[0].first / rst[0].second;
for (auto& p : rst)
{
r = p.first - q * p.second;
p.first = p.second;
p.second = r;
}
}
return { rst[0].first, rst[1].first, rst[2].first };
}

View File

@@ -84,3 +84,8 @@ Point2& Point2::operator*=(const int rhs)
*this = *this * rhs;
return *this;
}
int& Point2::operator[](size_t coordinateIndex)
{
return coordinateIndex == 0 ? x : y;
}

View File

@@ -15,14 +15,17 @@
#include <aoc/RestroomRedoubt.hpp>
#include <algorithm>
#include <array>
#include <functional>
#include <numeric>
#include <sstream>
#include <aoc/Math.hpp>
#include <aoc/Point2.hpp>
RestroomRedoubt::RestroomRedoubt(const int width, const int height)
: width_{ width }, height_{ height }, halfWidth_{ width / 2 }, halfHeight_{ height / 2 }
RestroomRedoubt::RestroomRedoubt(const int width, const int height, const bool runPart2)
: width_{ width }, height_{ height }, halfWidth_{ width / 2 }, halfHeight_{ height / 2 }, runPart2_{ runPart2 }
{
quadrants_.fill(0);
}
@@ -41,10 +44,10 @@ void RestroomRedoubt::processDataLine(const std::string& line)
{
std::istringstream stream{ line };
char c;
Point2 p;
Point2 v;
Point2 p, v;
stream >> c >> c >> p.x >> c >> p.y >> c >> c >> v.x >> c >> v.y;
// Calculates the resulting position, and shifts negative results back into the target range.
Point2 position = p + v * getNPredictionSeconds();
position.x %= width_;
if (position.x < 0)
@@ -57,6 +60,7 @@ void RestroomRedoubt::processDataLine(const std::string& line)
position.y += height_;
}
// Determines the resulting quadrant from the position.
if (position.x < halfWidth_)
{
if (position.y < halfHeight_)
@@ -79,14 +83,72 @@ void RestroomRedoubt::processDataLine(const std::string& line)
quadrants_[3]++;
}
}
// Keeps track of the robot for part 2.
robots_.push_back({ p, v });
}
void RestroomRedoubt::finish()
{
part1 = std::accumulate(quadrants_.cbegin(), quadrants_.cend(), 1, std::multiplies<int>{});
if (runPart2_)
{
findEasterEgg();
}
}
constexpr int RestroomRedoubt::getNPredictionSeconds()
{
return 100;
}
constexpr int RestroomRedoubt::getEasterEggThreshold()
{
return 25;
}
void RestroomRedoubt::findEasterEgg()
{
std::array<int, 2> patterns{ 0, 0 };
std::array<int, 2> sizes{ width_, height_ };
for (size_t i = 0; i < 2; i++)
{
int seconds{ 0 };
while (patterns[i] == 0)
{
std::vector<int> buckets(sizes[i], 0);
for (auto& robot : robots_)
{
buckets[robot.first[i]]++;
robot.first[i] += robot.second[i];
if (robot.first[i] < 0)
{
robot.first[i] += sizes[i];
}
else if (robot.first[i] >= sizes[i])
{
robot.first[i] -= sizes[i];
}
}
auto it = std::find_if(buckets.cbegin(), buckets.cend(), [](int x) { return x > getEasterEggThreshold(); });
if (it != buckets.cend())
{
patterns[i] = seconds;
}
seconds++;
}
}
// Determines the iteration where the repeating patterns of horizontal and vertical bands overlap, that is
// patterns_0 + width * x = patterns_1 + height * y
// for two positive integers 'x' and 'y'. This is equivalent to finding 'x' such that
// width * x ≡ patterns_1 - patterns_0 (modulo height)
// We can determine the modular multiplicative inverse of 'width' modulo 'height' with the extended Euclidean
// algorithm.
const auto [gcd, invHeight, invWidth] = Math::extendedEuclid(height_, width_);
// With that we can find 'x'.
int x{ (invWidth * (patterns[1] - patterns[0])) % height_ };
// And calculate when the overlap occurs.
part2 = patterns[0] + width_ * x;
}