138 lines
3.6 KiB
C++
138 lines
3.6 KiB
C++
#include <algorithm>
|
|
#include <iostream>
|
|
#include <utility>
|
|
#include <numeric>
|
|
#include <set>
|
|
#include <map>
|
|
|
|
#include <aocpp/Startup.hpp>
|
|
#include <aocpp/Coord.hpp>
|
|
#include <aocpp/Grid.hpp>
|
|
|
|
using namespace aocpp;
|
|
|
|
namespace {
|
|
|
|
template<typename T>
|
|
struct Rational {
|
|
T numerator, denominator;
|
|
|
|
Rational() : numerator{0}, denominator{1} {}
|
|
Rational(T x) : numerator{x}, denominator{1} {}
|
|
Rational(T n, T d) {
|
|
if (d == 0) { numerator = 1; denominator = 0; return; }
|
|
if (d < 0) { d = -d; n = -n; }
|
|
T m = std::gcd(n,d);
|
|
numerator = n / m;
|
|
denominator = d / m;
|
|
}
|
|
auto constexpr operator<=>(Rational const& rhs) const -> std::strong_ordering {
|
|
return numerator * rhs.denominator <=> rhs.numerator * denominator;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
auto operator<<(std::ostream & out, Rational<T> const& r) -> std::ostream & {
|
|
return out << r.numerator << "/" << r.denominator;
|
|
}
|
|
|
|
/// Map a vector to a rational number such that the numbers are
|
|
/// ordered starting with up and proceeding clockwise.
|
|
/// Each quadrant is mapped to a whole number. Slopes in that
|
|
/// quadrant are mapped to a fraction between 0 and 1.
|
|
auto angle(Coord c) -> Rational<std::int64_t> {
|
|
auto mk = [](std::int64_t q, std::int64_t a, std::int64_t b) {
|
|
return Rational{q*(a+b)-b, a+b};
|
|
};
|
|
return
|
|
c.x == 0 && c.y == 0 ? -1 :
|
|
c.x >= 0 && c.y < 0 ? mk(1, c.x, -c.y) :
|
|
c.x > 0 && c.y >= 0 ? mk(2, c.y, c.x) :
|
|
c.x <= 0 && c.y > 0 ? mk(3,-c.x, c.y) :
|
|
mk(4, c.y, c.x);
|
|
}
|
|
|
|
auto ClearView(Grid const& grid, Coord src, Coord dst) -> bool {
|
|
auto vec = dst - src;
|
|
auto steps = std::gcd(vec.x, vec.y);
|
|
Coord delta {vec.x / steps, vec.y / steps};
|
|
for (auto cursor = src+delta; cursor != dst; cursor+=delta) {
|
|
if (grid[cursor] == '#') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto Part1(Grid const& grid) {
|
|
std::size_t best = 0;
|
|
Coord base {};
|
|
grid.each([&](Coord src, char s) {
|
|
if ('#' == s) {
|
|
std::size_t visible = 0;
|
|
grid.each([&](Coord dst, char d){
|
|
if ('#' == d && src != dst) {
|
|
if (ClearView(grid, src, dst)) {
|
|
visible++;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (visible > best) {
|
|
best = visible;
|
|
base = src;
|
|
}
|
|
}
|
|
});
|
|
return std::make_pair(best, base);
|
|
}
|
|
|
|
auto Part2(Grid const& grid, Coord base) {
|
|
std::map<Rational<std::int64_t>, std::vector<Coord>> targets;
|
|
|
|
// arrange all the other asteroids by their angle relative to the base
|
|
grid.each([&](Coord c, char v){
|
|
if (c != base && v == '#') {
|
|
targets[angle(c-base)].push_back(c);
|
|
}
|
|
});
|
|
|
|
// Sort to vectors per angle such that the nearest asteroids are at
|
|
// the end of the list
|
|
for (auto & [_, vec] : targets) {
|
|
std::sort(vec.begin(), vec.end(), [](auto const& x, auto const& y) {
|
|
return Norm1(x) > Norm1(y);
|
|
});
|
|
}
|
|
|
|
// Remove 199 asteroids from the asteroid list
|
|
auto cursor = targets.begin();
|
|
for (std::size_t n = 199; n > 0; n--) {
|
|
if (targets.empty()) { throw std::runtime_error{"no solution to part 2"}; }
|
|
|
|
cursor->second.pop_back();
|
|
if (cursor->second.empty()) {
|
|
cursor = targets.erase(cursor);
|
|
} else {
|
|
cursor++;
|
|
}
|
|
|
|
if (cursor == targets.end()) {
|
|
cursor = targets.begin();
|
|
}
|
|
}
|
|
|
|
// Report the location of the 200th asteroid
|
|
auto const& asteroid = cursor->second.back();
|
|
return 100 * asteroid.x + asteroid.y;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main(int argc, char** argv) -> int {
|
|
auto grid = Grid::Parse(Startup(argc, argv));
|
|
auto [part1, base] = Part1(grid);
|
|
std::cout << "Part 1: " << part1 << std::endl;
|
|
std::cout << "Part 2: " << Part2(grid, base) << std::endl;
|
|
}
|