2022-11-11 21:39:36 -08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <iostream>
|
2022-11-13 20:14:46 -08:00
|
|
|
#include <map>
|
2022-11-11 21:39:36 -08:00
|
|
|
#include <numeric>
|
|
|
|
#include <set>
|
2022-11-13 20:14:46 -08:00
|
|
|
#include <sstream>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <doctest.h>
|
2022-11-11 21:39:36 -08:00
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2022-11-12 08:03:14 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-11-11 21:39:36 -08:00
|
|
|
auto Part1(Grid const& grid) {
|
|
|
|
std::size_t best = 0;
|
|
|
|
Coord base {};
|
2024-09-05 20:10:34 -07:00
|
|
|
for (auto [src, s] : grid) {
|
2022-11-11 21:39:36 -08:00
|
|
|
if ('#' == s) {
|
2022-11-12 08:03:14 -08:00
|
|
|
std::size_t visible = 0;
|
2024-09-05 20:10:34 -07:00
|
|
|
for (auto [dst, d] : grid) {
|
2022-11-11 21:39:36 -08:00
|
|
|
if ('#' == d && src != dst) {
|
2022-11-12 08:03:14 -08:00
|
|
|
if (ClearView(grid, src, dst)) {
|
|
|
|
visible++;
|
|
|
|
}
|
2022-11-11 21:39:36 -08:00
|
|
|
}
|
2024-09-05 20:10:34 -07:00
|
|
|
}
|
2022-11-12 08:03:14 -08:00
|
|
|
|
|
|
|
if (visible > best) {
|
|
|
|
best = visible;
|
2022-11-11 21:39:36 -08:00
|
|
|
base = src;
|
|
|
|
}
|
|
|
|
}
|
2024-09-05 20:10:34 -07:00
|
|
|
}
|
2022-11-11 21:39:36 -08:00
|
|
|
return std::make_pair(best, base);
|
|
|
|
}
|
|
|
|
|
2022-11-13 20:14:46 -08:00
|
|
|
auto Part2(Grid const& grid, Coord base, std::size_t n) {
|
2022-11-11 21:39:36 -08:00
|
|
|
std::map<Rational<std::int64_t>, std::vector<Coord>> targets;
|
|
|
|
|
|
|
|
// arrange all the other asteroids by their angle relative to the base
|
2024-09-05 20:10:34 -07:00
|
|
|
for (auto [c, v] : grid){
|
2022-11-11 21:39:36 -08:00
|
|
|
if (c != base && v == '#') {
|
|
|
|
targets[angle(c-base)].push_back(c);
|
|
|
|
}
|
2024-09-05 20:10:34 -07:00
|
|
|
}
|
2022-11-11 21:39:36 -08:00
|
|
|
|
|
|
|
// 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();
|
2022-11-13 20:14:46 -08:00
|
|
|
for (; n > 1; n--) {
|
2022-11-11 21:39:36 -08:00
|
|
|
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
|
|
|
|
|
2022-11-13 20:14:46 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-31 09:15:15 -08:00
|
|
|
auto Main(std::istream & in, std::ostream & out) -> void
|
2023-01-31 08:58:42 -08:00
|
|
|
{
|
|
|
|
auto const grid {Grid::Parse(in)};
|
|
|
|
auto const [part1, base] = Part1(grid);
|
2023-01-31 09:15:15 -08:00
|
|
|
out << "Part 1: " << part1 << std::endl;
|
|
|
|
out << "Part 2: " << Part2(grid, base, 200) << std::endl;
|
2022-11-11 21:39:36 -08:00
|
|
|
}
|