#include #include #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 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::size_t n) { 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 (; n > 1; 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 TEST_SUITE("documented examples") { char const* bigexample = ".#..##.###...#######\n" "##.############..##.\n" ".#.######.########.#\n" ".###.#######.####.#.\n" "#####.##.#.##.###.##\n" "..#####..#.#########\n" "####################\n" "#.####....###.#.#.##\n" "##.#################\n" "#####.##.###..####..\n" "..######..##.#######\n" "####.##.####...##..#\n" ".#####..#.######.###\n" "##...#.##########...\n" "#.##########.#######\n" ".####.#.###.###.#.##\n" "....##.##.###..#####\n" ".#.#.###########.###\n" "#.#.#.#####.####.###\n" "###.##.####.##.#..##\n"; TEST_CASE("part 1") { auto test = [](Coord base, std::size_t count, std::string text) { std::istringstream in {std::move(text)}; auto [count_, base_] = Part1(Grid::Parse(in)); REQUIRE(count_ == count); REQUIRE(base_ == base); }; test({3,4}, 8, ".#..#\n" ".....\n" "#####\n" "....#\n" "...##\n"); test({1,2}, 35, "#.#...#.#.\n" ".###....#.\n" ".#....#...\n" "##.#.#.#.#\n" "....#.#.#.\n" ".##..###.#\n" "..#...##..\n" "..##....##\n" "......#...\n" ".####.###.\n"); test({6,3}, 41, ".#..#..###\n" "####.###.#\n" "....###.#.\n" "..###.##.#\n" "##.##.#.#.\n" "....###..#\n" "..#.#..#.#\n" "#..#.#.###\n" ".##...##.#\n" ".....#.#..\n"); test({11,13}, 210, bigexample); } TEST_CASE("part 2") { auto test = [](std::size_t answer, Coord base, std::size_t nth, std::string text) { std::istringstream in {std::move(text)}; REQUIRE(answer == Part2(Grid::Parse(in), base, nth)); }; test(1403, {8,3}, 36, ".#....#####...#..\n" "##...##.#####..##\n" "##...#...#.#####.\n" "..#.....X...###..\n" "..#.#.....#....##\n"); test(802, {11,13}, 200, bigexample); } } 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, 200) << std::endl; }