diff --git a/2022/13.cpp b/2022/13.cpp index e3a8c4b..0cd9806 100644 --- a/2022/13.cpp +++ b/2022/13.cpp @@ -18,83 +18,99 @@ namespace { namespace phx = boost::phoenix; namespace qi = boost::spirit::qi; -struct Tree; -using Trees = std::vector; -using Input = std::vector>; +struct Packet; +using Packets = std::vector; +using PacketPair = std::array; +using PacketPairs = std::vector; -struct Tree { - std::variant rep; +// A packet is an integer or a list of packets. +struct Packet { + std::variant rep; }; -auto operator<(Tree const& x, std::int64_t y) -> bool; -auto operator<(std::int64_t x, Tree const& y) -> bool; +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 x, Trees const& y) -> bool { - switch (y.size()) { +auto operator<(std::int64_t const lhs, Packets const& rhs) -> bool { + switch (rhs.size()) { case 0: return false; - case 1: return x < y[0]; - default: return !(y[0] < x); + case 1: return lhs < rhs[0]; + default: return !(rhs[0] < lhs); } } -auto operator<(Trees const& x, std::int64_t y) -> bool { - switch (x.size()) { +auto operator<(Packets const& lhs, std::int64_t const rhs) -> bool { + switch (lhs.size()) { case 0: return true; - default: return x[0] < y; + default: return lhs[0] < rhs; } } -auto operator<(Tree const& x, std::int64_t y) -> bool { - return std::visit([y](auto const& x_) { return x_ < y; }, x.rep); +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 x, Tree const& y) -> bool { - return std::visit([x](auto const& y_) { return x < y_; }, y.rep); +auto operator<(std::int64_t lhs, Packet const& rhs) -> bool { + return std::visit([lhs](auto const& rep) { return lhs < rep; }, rhs.rep); } -auto operator<(Tree const& lhs, Tree const& rhs) -> bool { +// 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 leaf; - qi::rule trees; - qi::rule tree; - qi::rule()> treepair; - qi::rule input; +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{input} { + Grammar() : Grammar::base_type{packetPairs} { using namespace qi::labels; - leaf = qi::long_long; - trees = "[" >> -(tree % ",") >> "]"; - tree = leaf [ bind(&Tree::rep, _val) = _1 ] - | trees [ bind(&Tree::rep, _val) = _1 ]; - treepair = (tree >> "\n" >> tree >> "\n") [(_val[0] = _1, _val[1] = _2)]; - input = -(treepair % "\n"); + 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"); } }; -auto Parse(std::istream & in) -> Input +/// 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 { - Input result; - std::string content {std::istreambuf_iterator{in}, {}}; - auto b {content.begin()}; - auto const e {content.end()}; + 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{"tree parser failed"}; + throw std::runtime_error{"Bad packet: " + content}; } return result; } -auto Part1(Input const& input) -> std::size_t +// 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 { - std::int64_t result {0}; - for (auto const& [i,pair] : boost::adaptors::index(input)) { + auto result = std::int64_t{0}; + for (auto const& [i, pair] : boost::adaptors::index(packetPairs)) { if (pair[0] < pair[1]) { result += i + 1; } @@ -102,15 +118,20 @@ auto Part1(Input const& input) -> std::size_t return result; } -auto Part2(Input const& input) -> std::size_t +// 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 { - Tree two{2}, six{6}; // Indistinguishable from [[2]] and [[6]] - std::size_t ix2{1}, ix6{2}; - for (auto const& pair : input) { - for (auto const& tree : pair) { - if (tree < six) { + 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 (tree < two) { + if (Packet < two) { ix2++; } } @@ -123,20 +144,20 @@ auto Part2(Input const& input) -> std::size_t TEST_SUITE("2022-13 examples") { TEST_CASE("simple ordering") { - CHECK(Tree{1} < Tree{2}); - CHECK(!(Tree{2} < Tree{1})); - CHECK(Tree{Trees{Tree{1}}} < Tree{Trees{Tree{2}}}); - CHECK(Tree{1} < Tree{Trees{Tree{2}}}); - CHECK(!(Tree{Trees{Tree{2}}} < Tree{1})); - CHECK(Tree{Trees{}} < Tree{Trees{Tree{2}}}); - CHECK(Tree{Trees{}} < Tree{Trees{Tree{2}}}); - CHECK(Tree{1} < Tree{Trees{Tree{2},Tree{3}}}); - CHECK(!(Tree{Trees{Tree{2},Tree{3}}} < Tree{1})); - CHECK(Tree{2} < Tree{Trees{Tree{2},Tree{3}}}); - CHECK(!(Tree{Trees{Tree{2},Tree{3}}} < Tree{2})); + 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") { - std::istringstream in { + auto in = std::istringstream{ "[1,1,3,1,1]\n" "[1,1,5,1,1]\n" "\n" @@ -161,15 +182,15 @@ TEST_SUITE("2022-13 examples") { "[1,[2,[3,[4,[5,6,7]]]],8,9]\n" "[1,[2,[3,[4,[5,6,0]]]],8,9]\n" }; - auto const input = Parse(in); - CHECK(13 == Part1(input)); - CHECK(140 == Part2(input)); + auto const PacketPairs = Parse(in); + CHECK(13 == Part1(PacketPairs)); + CHECK(140 == Part2(PacketPairs)); } } auto Main(std::istream & in, std::ostream & out) -> void { - auto const input = Parse(in); - out << "Part 1: " << Part1(input) << std::endl; - out << "Part 2: " << Part2(input) << std::endl; + auto const PacketPairs = Parse(in); + out << "Part 1: " << Part1(PacketPairs) << std::endl; + out << "Part 2: " << Part2(PacketPairs) << std::endl; }