comments
This commit is contained in:
parent
a6b6ea5ed0
commit
a5fe5c007f
153
2022/13.cpp
153
2022/13.cpp
@ -18,83 +18,99 @@ namespace {
|
||||
namespace phx = boost::phoenix;
|
||||
namespace qi = boost::spirit::qi;
|
||||
|
||||
struct Tree;
|
||||
using Trees = std::vector<Tree>;
|
||||
using Input = std::vector<std::array<Tree, 2>>;
|
||||
struct Packet;
|
||||
using Packets = std::vector<Packet>;
|
||||
using PacketPair = std::array<Packet, 2>;
|
||||
using PacketPairs = std::vector<PacketPair>;
|
||||
|
||||
struct Tree {
|
||||
std::variant<std::int64_t, Trees> rep;
|
||||
// A packet is an integer or a list of packets.
|
||||
struct Packet {
|
||||
std::variant<std::int64_t, Packets> 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 <typename It>
|
||||
class Grammar : public qi::grammar<It, Input()> {
|
||||
qi::rule<It, std::int64_t> leaf;
|
||||
qi::rule<It, Trees()> trees;
|
||||
qi::rule<It, Tree()> tree;
|
||||
qi::rule<It, std::array<Tree, 2>()> treepair;
|
||||
qi::rule<It, Input()> input;
|
||||
class Grammar : public qi::grammar<It, PacketPairs()> {
|
||||
qi::rule<It, std::int64_t> integer;
|
||||
qi::rule<It, Packets()> packets;
|
||||
qi::rule<It, Packet()> packet;
|
||||
qi::rule<It, PacketPair()> packetPair;
|
||||
qi::rule<It, PacketPairs()> 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<decltype(b)>{}, 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user