#include #include #include #include #include #include #include #include #include #include #include namespace { namespace phx = boost::phoenix; namespace qi = boost::spirit::qi; using Input = std::vector>; const aocpp::Coord TOP {500, 0}; // Input file grammar template class Grammar : public qi::grammar { qi::rule coord; qi::rule()> line; qi::rule input; public: Grammar() : Grammar::base_type{input} { using namespace qi::labels; coord = qi::long_long [ phx::bind(&aocpp::Coord::x, _val) = _1 ] >> "," >> qi::long_long [ phx::bind(&aocpp::Coord::y, _val) = _1 ]; line = coord % " -> " >> "\n"; input = *line; } }; /// Parse the complete input stream according to the rules defined in 'Grammar'. /// Throws: std::runtime_error on failed parse auto Parse(std::istream & in) -> Input { auto result = Input{}; auto const content = std::string{std::istreambuf_iterator{in}, {}}; auto b = content.begin(); // updated on successful parse auto const e = content.end(); if (!qi::parse(b, e, Grammar{}, result) || b != e) { throw std::runtime_error{"Bad input file: " + content}; } return result; } auto ExpandLines(Input const& lines) -> std::set { auto result = std::set{}; for (auto const& line : lines) { for (auto const i : boost::irange(std::size_t{1}, line.size())) { auto const& a = line[i-1]; auto const& b = line[i]; if (a.x == b.x) { for (auto const y : boost::irange(std::min(a.y, b.y), 1+std::max(a.y, b.y))) { result.insert({a.x, y}); } } else if (a.y == b.y) { for (auto const x : boost::irange(std::min(a.x, b.x), 1+std::max(a.x, b.x))) { result.insert({x, a.y}); } } else { throw new std::runtime_error{"Bad line segment"}; } } } return result; } auto FindBottom(Input const& input) -> std::int64_t { auto result = std::int64_t{TOP.y}; for (auto const& line : input) { for (auto const& coord : line) { result = std::max(result, coord.y); } } return result; } auto Part1(std::int64_t bottom, std::set world) -> std::size_t { auto path = std::vector{TOP}; auto const starting_size = world.size(); while (!path.empty()) { auto & here = path.back(); if (here.y == bottom) { return world.size() - starting_size; } else if (!world.contains(aocpp::Down(here))) { path.push_back(aocpp::Down(here)); } else if (!world.contains(aocpp::Left(aocpp::Down(here)))) { path.push_back(aocpp::Left(aocpp::Down(here))); } else if (!world.contains(aocpp::Right(aocpp::Down(here)))) { path.push_back(aocpp::Right(aocpp::Down(here))); } else { world.insert(here); path.pop_back(); } } return world.size() - starting_size; } auto Part2(std::int64_t bottom, std::set world) -> std::size_t { auto path = std::vector{TOP}; auto const starting_size = world.size(); while (!path.empty()) { auto & here = path.back(); if (here.y == 1 + bottom) { world.insert(here); path.pop_back(); } else if (!world.contains(aocpp::Down(here))) { path.push_back(aocpp::Down(here)); } else if (!world.contains(aocpp::Left(aocpp::Down(here)))) { path.push_back(aocpp::Left(aocpp::Down(here))); } else if (!world.contains(aocpp::Right(aocpp::Down(here)))) { path.push_back(aocpp::Right(aocpp::Down(here))); } else { world.insert(here); path.pop_back(); } } return world.size() - starting_size; } } // namespace TEST_SUITE("2022-14 examples") { TEST_CASE("documented example") { auto in = std::istringstream{ "498,4 -> 498,6 -> 496,6\n" "503,4 -> 502,4 -> 502,9 -> 494,9\n" }; auto const input = Parse(in); auto const bottom = FindBottom(input); auto world = ExpandLines(input); CHECK(24 == Part1(bottom, world)); CHECK(93 == Part2(bottom, std::move(world))); } } auto Main(std::istream & in, std::ostream & out) -> void { auto const input = Parse(in); auto const bottom = FindBottom(input); auto world = ExpandLines(input); out << "Part 1: " << Part1(bottom, world) << std::endl; out << "Part 2: " << Part2(bottom, std::move(world)) << std::endl; }