193 lines
4.3 KiB
C++
193 lines
4.3 KiB
C++
#include <algorithm>
|
|
#include <bitset>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iterator>
|
|
#include <vector>
|
|
#include <cinttypes>
|
|
|
|
#include <doctest.h>
|
|
|
|
#include <aocpp/Startup.hpp>
|
|
#include <dlx.hpp>
|
|
using dlx::Dlx;
|
|
|
|
namespace {
|
|
|
|
using ValueType = std::uint64_t;
|
|
using FieldInfo = std::tuple<std::string, ValueType, ValueType, ValueType, ValueType>;
|
|
struct Input {
|
|
std::vector<FieldInfo> ranges;
|
|
std::vector<ValueType> your_ticket;
|
|
std::vector<std::vector<ValueType>> nearby_tickets;
|
|
};
|
|
|
|
auto ParseTicket(std::string && str) -> std::vector<ValueType>
|
|
{
|
|
std::istringstream in{std::move(str)};
|
|
std::vector<ValueType> result;
|
|
|
|
std::string word;
|
|
while(std::getline(in, word, ',')) {
|
|
result.push_back(std::stoull(word));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
auto InRange(
|
|
FieldInfo const& range,
|
|
ValueType value
|
|
) -> bool
|
|
{
|
|
auto const& [_,lo1,hi1,lo2,hi2] = range;
|
|
return lo1 <= value && value <= hi1 || lo2 <= value && value <= hi2;
|
|
}
|
|
|
|
auto Parse(std::istream & in) -> Input
|
|
{
|
|
Input input;
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(in, line)) {
|
|
if (line.empty()) break;
|
|
|
|
auto start = line.find(':');
|
|
if (start == std::string::npos || start + 2 > line.size()) {
|
|
throw std::runtime_error{"bad input"};
|
|
}
|
|
|
|
ValueType lo1, hi1, lo2, hi2;
|
|
auto res = std::sscanf(
|
|
line.c_str() + start + 2,
|
|
"%" PRIu64 "-%" PRIu64 " or %" PRIu64 "-%" PRIu64,
|
|
&lo1, &hi1, &lo2, &hi2);
|
|
if (res != 4) throw std::runtime_error{"bad input"};
|
|
|
|
input.ranges.emplace_back(line.substr(0, start), lo1, hi1, lo2, hi2);
|
|
}
|
|
|
|
if (!std::getline(in, line) ||
|
|
line != "your ticket:" ||
|
|
!std::getline(in, line)) {
|
|
throw std::runtime_error{"bad input"};
|
|
}
|
|
|
|
input.your_ticket = ParseTicket(std::move(line));
|
|
|
|
if (!std::getline(in, line) ||
|
|
!line.empty() ||
|
|
!std::getline(in, line) ||
|
|
line != "nearby tickets:") {
|
|
throw std::runtime_error{"bad input"};
|
|
}
|
|
|
|
while (std::getline(in, line)) {
|
|
input.nearby_tickets.emplace_back(ParseTicket(std::move(line)));
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
// Computes error rate and removes invalid tickets!
|
|
auto Part1(Input & input) -> ValueType {
|
|
ValueType error_rate = 0;
|
|
|
|
auto const is_good_value = [&](ValueType value) {
|
|
return std::any_of(
|
|
input.ranges.begin(), input.ranges.end(),
|
|
[value](auto const& x) {
|
|
return InRange(x, value);
|
|
});
|
|
};
|
|
|
|
auto it = std::remove_if(
|
|
input.nearby_tickets.begin(), input.nearby_tickets.end(),
|
|
[&](auto const& ticket) {
|
|
auto remove_ticket = false;
|
|
for (auto const value : ticket) {
|
|
if (!is_good_value(value)) {
|
|
error_rate += value;
|
|
remove_ticket = true;
|
|
}
|
|
}
|
|
return remove_ticket;
|
|
});
|
|
input.nearby_tickets.erase(it, input.nearby_tickets.end());
|
|
return error_rate;
|
|
}
|
|
|
|
|
|
auto Part2(Input const& input)
|
|
{
|
|
// number of ranges in input
|
|
auto const n = input.ranges.size();
|
|
|
|
// Rows: field_id + n * field_position
|
|
// Cols: fields then positions
|
|
Dlx dlx;
|
|
|
|
for (std::size_t i = 0; i < n; i++) {
|
|
for (std::size_t j = 0; j < n; j++) {
|
|
auto const possible =
|
|
std::all_of(
|
|
input.nearby_tickets.begin(),
|
|
input.nearby_tickets.end(),
|
|
[&](auto const& ticket){
|
|
return InRange(input.ranges[i], ticket[j]);
|
|
});
|
|
if (possible) {
|
|
dlx.Set(i + n*j, i);
|
|
dlx.Set(i + n*j, n + j);
|
|
}
|
|
}
|
|
}
|
|
|
|
ValueType result = 1;
|
|
dlx::ForallCover(dlx, [&](auto const& sln) {
|
|
for (auto const x : sln) {
|
|
auto const field_id = x%n;
|
|
auto const field_position = x/n;
|
|
if (std::get<0>(input.ranges[field_id]).starts_with("departure")){
|
|
result *= input.your_ticket[field_position];
|
|
}
|
|
}
|
|
return true; // early exit
|
|
});
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
TEST_SUITE("documented examples") {
|
|
TEST_CASE("part 1") {
|
|
std::istringstream in {
|
|
"class: 1-3 or 5-7\n"
|
|
"row: 6-11 or 33-44\n"
|
|
"seat: 13-40 or 45-50\n"
|
|
"\n"
|
|
"your ticket:\n"
|
|
"7,1,14\n"
|
|
"\n"
|
|
"nearby tickets:\n"
|
|
"7,3,47\n"
|
|
"40,4,50\n"
|
|
"55,2,20\n"
|
|
"38,6,12\n"
|
|
};
|
|
|
|
auto input = Parse(in);
|
|
CHECK(Part1(input) == 71);
|
|
}
|
|
}
|
|
|
|
auto Main(std::istream & in, std::ostream & out) -> void
|
|
{
|
|
auto input {Parse(in)};
|
|
out << "Part 1: " << Part1(input) << std::endl;
|
|
out << "Part 2: " << Part2(input) << std::endl;
|
|
}
|