182 lines
5.2 KiB
C++
182 lines
5.2 KiB
C++
#include <array>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
#include <boost/phoenix.hpp>
|
|
#include <boost/range/adaptor/indexed.hpp>
|
|
#include <boost/spirit/include/qi.hpp>
|
|
|
|
#include <doctest.h>
|
|
|
|
#include <aocpp/Parsing.hpp>
|
|
#include <aocpp/Startup.hpp>
|
|
|
|
namespace {
|
|
|
|
namespace phx = boost::phoenix;
|
|
namespace qi = boost::spirit::qi;
|
|
|
|
struct Packet;
|
|
using Packets = std::vector<Packet>;
|
|
using PacketPair = std::array<Packet, 2>;
|
|
using PacketPairs = std::vector<PacketPair>;
|
|
|
|
// A packet is an integer or a list of packets.
|
|
struct Packet {
|
|
std::variant<std::int64_t, Packets> 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.
|
|
class Grammar : public qi::grammar<std::string::const_iterator, PacketPairs()> {
|
|
qi::rule<iterator_type, std::int64_t> integer;
|
|
qi::rule<iterator_type, Packets()> packets;
|
|
qi::rule<iterator_type, Packet()> packet;
|
|
qi::rule<iterator_type, PacketPair()> packetPair;
|
|
qi::rule<iterator_type, PacketPairs()> 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");
|
|
}
|
|
};
|
|
|
|
// 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 = aocpp::ParseGrammar(Grammar{}, in);
|
|
CHECK(13 == Part1(packetPairs));
|
|
CHECK(140 == Part2(packetPairs));
|
|
}
|
|
}
|
|
|
|
auto Main(std::istream & in, std::ostream & out) -> void
|
|
{
|
|
auto const packetPairs = aocpp::ParseGrammar(Grammar{}, in);
|
|
out << "Part 1: " << Part1(packetPairs) << std::endl;
|
|
out << "Part 2: " << Part2(packetPairs) << std::endl;
|
|
}
|