aocpp/2019/10.cpp
2023-01-31 08:59:09 -08:00

218 lines
5.5 KiB
C++

#include <algorithm>
#include <iostream>
#include <map>
#include <numeric>
#include <set>
#include <sstream>
#include <utility>
#include <doctest.h>
#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::size_t n) {
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 (; 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(std::istream & in) -> void
{
auto const grid {Grid::Parse(in)};
auto const [part1, base] = Part1(grid);
std::cout << "Part 1: " << part1 << std::endl;
std::cout << "Part 2: " << Part2(grid, base, 200) << std::endl;
}