#include #include #include #include #include #include #include #include #include #include #include namespace { namespace phx = boost::phoenix; namespace qi = boost::spirit::qi; struct Packet; using Packets = std::vector; using PacketPair = std::array; using PacketPairs = std::vector; // A packet is an integer or a list of packets. struct Packet { std::variant rep; }; auto operator<(Packet const& lhs, std::int64_t rhs) -> bool; auto operator<(std::int64_t lhs, Packet const& rhs) -> bool; auto operator<(std::int64_t const lhs, Packets const& rhs) -> bool { switch (rhs.size()) { case 0: return false; case 1: return lhs < rhs[0]; default: return !(rhs[0] < lhs); } } auto operator<(Packets const& lhs, std::int64_t const rhs) -> bool { switch (lhs.size()) { case 0: return true; default: return lhs[0] < rhs; } } auto operator<(Packet const& lhs, std::int64_t rhs) -> bool { return std::visit([rhs](auto const& rep) { return rep < rhs; }, lhs.rep); } auto operator<(std::int64_t lhs, Packet const& rhs) -> bool { return std::visit([lhs](auto const& rep) { return lhs < rep; }, rhs.rep); } // Less-than relation on packets. // Two integers are compared using their integer values. // Two lists are compared using a lexicographic ordering. // When an integer is compared to a list, that integer is promoted to a singleton list. auto operator<(Packet const& lhs, Packet const& rhs) -> bool { return std::visit(std::less(), lhs.rep, rhs.rep); } // Input file grammar: // Each packet should be newline-terminated. // Each pair should be newline-separated. // Packets can be integer literals or a list of packets. // A list of packets is bracketed with square brackets and is comma-separated. // No additional whitespace is tolerated in the input stream. template class Grammar : public qi::grammar { qi::rule integer; qi::rule packets; qi::rule packet; qi::rule packetPair; qi::rule packetPairs; public: Grammar() : Grammar::base_type{packetPairs} { using namespace qi::labels; integer = qi::long_long; packets = "[" >> -(packet % ",") >> "]"; packet = integer [ bind(&Packet::rep, _val) = _1 ] | packets [ bind(&Packet::rep, _val) = _1 ]; packetPair = packet [_val[0] = _1] >> "\n" >> packet [_val[1] = _1] >> "\n"; packetPairs = -(packetPair % "\n"); } }; /// Parse the complete input stream according to the rules defined in 'Grammar'. /// Throws: std::runtime_error on failed parse auto Parse(std::istream & in) -> PacketPairs { auto result = PacketPairs{}; 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 packet: " + content}; } return result; } // Return the sum 1-based indexes of the pairs where the first element is less than the second. auto Part1(PacketPairs const& packetPairs) -> std::size_t { auto result = std::int64_t{0}; for (auto const& [i, pair] : boost::adaptors::index(packetPairs)) { if (pair[0] < pair[1]) { result += i + 1; } } return result; } // Returns the product of the 1-based indexes of the elements [[2]] and [[6]] // when those elements are added to all the packets in the packet pair list // and that list is then soted. auto Part2(PacketPairs const& packetPairs) -> std::size_t { auto two = Packet{2}; // Indistinguishable from [[2]] and [[6]] auto six = Packet{6}; auto ix2 = std::size_t{1}; auto ix6 = std::size_t{2}; for (auto const& pair : packetPairs) { for (auto const& Packet : pair) { if (Packet < six) { ix6++; if (Packet < two) { ix2++; } } } } return ix2 * ix6; } } // namespace TEST_SUITE("2022-13 examples") { TEST_CASE("simple ordering") { CHECK(Packet{1} < Packet{2}); CHECK(!(Packet{2} < Packet{1})); CHECK(Packet{Packets{Packet{1}}} < Packet{Packets{Packet{2}}}); CHECK(Packet{1} < Packet{Packets{Packet{2}}}); CHECK(!(Packet{Packets{Packet{2}}} < Packet{1})); CHECK(Packet{Packets{}} < Packet{Packets{Packet{2}}}); CHECK(Packet{Packets{}} < Packet{Packets{Packet{2}}}); CHECK(Packet{1} < Packet{Packets{Packet{2},Packet{3}}}); CHECK(!(Packet{Packets{Packet{2},Packet{3}}} < Packet{1})); CHECK(Packet{2} < Packet{Packets{Packet{2},Packet{3}}}); CHECK(!(Packet{Packets{Packet{2},Packet{3}}} < Packet{2})); } TEST_CASE("documented example") { auto in = std::istringstream{ "[1,1,3,1,1]\n" "[1,1,5,1,1]\n" "\n" "[[1],[2,3,4]]\n" "[[1],4]\n" "\n" "[9]\n" "[[8,7,6]]\n" "\n" "[[4,4],4,4]\n" "[[4,4],4,4,4]\n" "\n" "[7,7,7,7]\n" "[7,7,7]\n" "\n" "[]\n" "[3]\n" "\n" "[[[]]]\n" "[[]]\n" "\n" "[1,[2,[3,[4,[5,6,7]]]],8,9]\n" "[1,[2,[3,[4,[5,6,0]]]],8,9]\n" }; auto const PacketPairs = Parse(in); CHECK(13 == Part1(PacketPairs)); CHECK(140 == Part2(PacketPairs)); } } auto Main(std::istream & in, std::ostream & out) -> void { auto const PacketPairs = Parse(in); out << "Part 1: " << Part1(PacketPairs) << std::endl; out << "Part 2: " << Part2(PacketPairs) << std::endl; }