aocpp/2020/16.cpp
2023-01-31 08:59:09 -08:00

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) -> void
{
auto input {Parse(in)};
std::cout << "Part 1: " << Part1(input) << std::endl;
std::cout << "Part 2: " << Part2(input) << std::endl;
}