#include #include #include #include #include #include #include #include #include using namespace aocpp; namespace { template 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 auto operator<<(std::ostream & out, Rational 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 { 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 Part1(Grid const& grid) { std::size_t best = 0; Coord base {}; grid.each([&](Coord src, char s) { if ('#' == s) { std::set> angles; grid.each([&](Coord dst, char d){ if ('#' == d && src != dst) { auto delta = dst - src; angles.insert(angle(dst - src)); } }); auto n = angles.size(); if (n > best) { best = n; base = src; } } }); return std::make_pair(best, base); } auto Part2(Grid const& grid, Coord base) { std::map, std::vector> 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; }