diff --git a/2020/16.cpp b/2020/16.cpp new file mode 100644 index 0000000..3ce075f --- /dev/null +++ b/2020/16.cpp @@ -0,0 +1,191 @@ +#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); + REQUIRE(Part1(input) == 71); + } +} + +auto main(int argc, char** argv) -> int { + auto input = Parse(aocpp::Startup(argc, argv)); + std::cout << "Part 1: " << Part1(input) << std::endl; + std::cout << "Part 2: " << Part2(input) << std::endl; +} diff --git a/2020/21.cpp b/2020/21.cpp index f2d1aa0..0dd56e6 100644 --- a/2020/21.cpp +++ b/2020/21.cpp @@ -14,31 +14,17 @@ #include #include +#include #include using aocpp::Startup; +using aocpp::SplitOn; using dlx::Dlx; namespace { using Input = std::vector, std::vector>>; -auto SplitOn(std::string stuff, std::string sep) -> std::vector { - std::vector results; - std::size_t cursor = 0; - for (;;) { - auto i = stuff.find(sep, cursor); - if (i != std::string::npos) { - results.push_back(stuff.substr(cursor, i-cursor)); - cursor = i + sep.size(); - } else { - break; - } - } - results.push_back(stuff.substr(cursor)); - return results; -} - auto Parse(std::istream & in) -> Input { Input results; @@ -123,12 +109,15 @@ auto Part2(Input const& input) { } } + // rows: allergen + allergens * ingredient + // columns: all allergens, then all ingredients Dlx dlx; + for (auto const& [allergen, ingredients] : Arrange(input)) { - auto a_id = a_ids[allergen]; + auto const a_id = a_ids[allergen]; for (auto const& ingredient : ingredients) { - auto i_id = i_ids[ingredient]; - auto row_id = a_id + a_ids.size() * i_id; + auto const i_id = i_ids[ingredient]; + auto const row_id = a_id + a_ids.size() * i_id; dlx.Set(row_id, a_id); dlx.Set(row_id, a_ids.size() + i_id); } @@ -139,22 +128,21 @@ auto Part2(Input const& input) { dlx.MarkOptional(a_ids.size() + n); } - std::vector solution; - dlx::ForallCover(dlx, [&](auto & sln){ solution = sln; }); - // extract assignment map from dlx row ids std::map entries; - for (auto const row : solution) { - entries.emplace(id_to_a[row % a_ids.size()], id_to_i[row / a_ids.size()]); - } + dlx::ForallCover(dlx, [&](auto & sln){ + for (auto const row : sln) { + entries.emplace(id_to_a[row % a_ids.size()], id_to_i[row / a_ids.size()]); + } + return true; // exit early + }); // build comma-separated list of ingredients std::string result; for (auto const & [_,i] : entries) { + if (!result.empty()) result += ','; result += i; - result += ','; } - result.pop_back(); return result; } diff --git a/2020/CMakeLists.txt b/2020/CMakeLists.txt index 752953b..2a3ee47 100644 --- a/2020/CMakeLists.txt +++ b/2020/CMakeLists.txt @@ -4,5 +4,8 @@ target_link_libraries(2020_02 aocpp) add_executable(2020_03 03.cpp) target_link_libraries(2020_03 aocpp) +add_executable(2020_16 16.cpp) +target_link_libraries(2020_16 aocpp dlx) + add_executable(2020_21 21.cpp) target_link_libraries(2020_21 aocpp dlx) \ No newline at end of file diff --git a/dlx/include/dlx.hpp b/dlx/include/dlx.hpp index 986a3ac..6f29c88 100644 --- a/dlx/include/dlx.hpp +++ b/dlx/include/dlx.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace dlx { @@ -39,8 +40,8 @@ public: // Increases the number of rows and columns if necessary. auto Set(std::size_t row, std::size_t col) -> void; -// Marks a column as optional: a solution need not cover the given column, -// but it still must respect the constraints it entails. + // Marks a column as optional: a solution need not cover the given column, + // but it still must respect the constraints it entails. auto MarkOptional(std::size_t col) -> void; // Removes a row from consideration. @@ -53,19 +54,19 @@ public: auto PickRow(std::size_t row) -> void; auto Solve( - std::function try_cb, - std::function undo_cb, - std::function found_cb, - std::function stuck_cb) -> void; + std::function try_cb, + std::function undo_cb, + std::function found_cb, + std::function stuck_cb) -> void; }; auto ForallCover(Dlx& dlx, auto cb) { std::vector sol; dlx.Solve( - [&](std::size_t c, std::size_t s, std::size_t r) { sol.push_back(r); }, - [&](){ sol.pop_back(); }, - [&](){ cb(sol); }, - [](std::size_t){} + [&](std::size_t c, std::size_t s, std::size_t r) { sol.push_back(r); return false; }, + [&](){ sol.pop_back(); return false; }, + [&](){ return cb(std::as_const(sol)); }, + [](std::size_t){ return false; } ); } diff --git a/dlx/src/dlx.cpp b/dlx/src/dlx.cpp index f93fe10..bf7a2d0 100644 --- a/dlx/src/dlx.cpp +++ b/dlx/src/dlx.cpp @@ -228,43 +228,42 @@ auto Dlx::RemoveRow(std::size_t i) -> void { } auto Dlx::Solve( - std::function try_cb, - std::function undo_cb, - std::function found_cb, - std::function stuck_cb) -> void + std::function try_cb, + std::function undo_cb, + std::function found_cb, + std::function stuck_cb) -> void { - auto const recurse = [&](auto const& self) -> void { + auto const recurse = [&](auto const& self) -> bool { auto c = root_->R; if (c == root_) { - found_cb(); - return; + return found_cb(); } - auto s = std::numeric_limits::max(); + auto s = c->s; for (auto const i : root_->rightwards()) { if (i->s < s) { s = (c = i)->s; - - if (s == 0) { - stuck_cb(c->n); - return; - } } } + if (s == 0) { + return stuck_cb(c->n); + } + c->CoverCol(); for (auto const r : c->downwards()) { - try_cb(c->n, s, r->n); + if (try_cb(c->n, s, r->n)) [[unlikely]] return true; for (auto const j : r->rightwards()) { j->c->CoverCol(); } - self(self); - undo_cb(); + if (self(self)) [[unlikely]] return true; + if (undo_cb()) [[unlikely]] return true; for (auto const j : r->leftwards()) { j->c->UncoverCol(); } } c->UncoverCol(); + return false; }; recurse(recurse); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6fb1e73..0ca5734 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,2 +1,2 @@ -add_library(aocpp src/Startup.cpp src/Coord.cpp) +add_library(aocpp src/Startup.cpp src/Coord.cpp src/Parsing.cpp) target_include_directories(aocpp PUBLIC include) \ No newline at end of file diff --git a/lib/include/aocpp/Parsing.hpp b/lib/include/aocpp/Parsing.hpp new file mode 100644 index 0000000..02b89ec --- /dev/null +++ b/lib/include/aocpp/Parsing.hpp @@ -0,0 +1,13 @@ +#ifndef AOCPP_PARSING_HPP_ +#define AOCPP_PARSING_HPP_ + +#include +#include + +namespace aocpp { + +auto SplitOn(std::string const& stuff, std::string const& sep) -> std::vector; + +} + +#endif \ No newline at end of file diff --git a/lib/src/Parsing.cpp b/lib/src/Parsing.cpp new file mode 100644 index 0000000..2cc057a --- /dev/null +++ b/lib/src/Parsing.cpp @@ -0,0 +1,22 @@ +#include + +namespace aocpp { + +auto SplitOn(std::string const& stuff, std::string const& sep) -> std::vector +{ + std::vector results; + std::size_t cursor = 0; + for (;;) { + auto i = stuff.find(sep, cursor); + if (i != std::string::npos) { + results.push_back(stuff.substr(cursor, i-cursor)); + cursor = i + sep.size(); + } else { + break; + } + } + results.push_back(stuff.substr(cursor)); + return results; +} + +} // namespace