#include #include #include #include #include #include #include #include #include #include #include #include using dlx::Dlx; namespace { using ValueType = std::uint64_t; using FieldInfo = std::tuple; struct Input { std::vector ranges; std::vector your_ticket; std::vector> nearby_tickets; }; auto ParseTicket(std::string && str) -> std::vector { std::istringstream in{std::move(str)}; std::vector 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; }