Add solution for "Day 14: Restroom Redoubt", part 2
This commit is contained in:
21
src/Math.cpp
21
src/Math.cpp
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user