From eac45695b5dfe22a1ecedce6020e91217b3b8a69 Mon Sep 17 00:00:00 2001 From: Eric Mertens Date: Sat, 19 Nov 2022 14:59:21 -0800 Subject: [PATCH] 2020-21 --- 2020/21.cpp | 197 ++++++++++++++++++++++++++++++++++++++++++++ 2020/CMakeLists.txt | 5 +- CMakeLists.txt | 1 + dlx/CMakeLists.txt | 2 + dlx/include/dlx.hpp | 113 +++++++++++++++++++++++++ dlx/src/dlx.cpp | 170 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 2020/21.cpp create mode 100644 dlx/CMakeLists.txt create mode 100644 dlx/include/dlx.hpp create mode 100644 dlx/src/dlx.cpp diff --git a/2020/21.cpp b/2020/21.cpp new file mode 100644 index 0000000..f2d1aa0 --- /dev/null +++ b/2020/21.cpp @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +using aocpp::Startup; +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; + std::string line; + while (std::getline(in, line)) { + char const sep[] = " (contains "; + auto i = line.find(sep); + if (i == std::string::npos || line.back() != ')') { throw std::runtime_error{"bad input"}; } + line.pop_back(); + results.emplace_back( + SplitOn(line.substr(0, i), " "), + SplitOn(line.substr(i + std::size(sep) - 1), ", ") + ); + } + return results; +} + +auto Arrange(Input const& input) -> std::map> +{ + std::map> possible; + + for (auto const& [ingredients, allergens] : input) { + for (auto const& allergen : allergens) { + auto ingredients_ = ingredients; + std::sort(ingredients_.begin(), ingredients_.end()); + + if (auto it = possible.find(allergen); it != possible.end()) { + auto & previous = it->second; + std::vector intersection; + std::set_intersection( + previous.begin(), previous.end(), + ingredients_.begin(), ingredients_.end(), + std::back_inserter(intersection) + ); + previous = std::move(intersection); + } else { + possible.emplace(allergen, std::move(ingredients_)); + } + } + } + return possible; +} + +auto Part1(Input const& input) -> std::size_t +{ + std::map> possible = Arrange(input); + std::map occurences; + + for (auto const& [ingredients, allergens] : input) { + for (auto const& ingredient : ingredients) { + occurences[ingredient]++; + } + } + + // Remove all candidates + for (auto const& [k,vs] : possible) { + for (auto const& v : vs) { + occurences.erase(v); + } + } + + std::size_t result = 0; + for (auto const& [_,v] : occurences) { + result += v; + } + + return result; +} + +auto Part2(Input const& input) { + std::map a_ids; + std::map i_ids; + std::vector id_to_a; + std::vector id_to_i; + + for (auto const& [is, as] : input) { + for (auto const& i : is) { + if (i_ids.emplace(i, i_ids.size()).second) { id_to_i.push_back(i); } + } + for (auto const& a : as) { + if (a_ids.emplace(a, a_ids.size()).second) { id_to_a.push_back(a); } + } + } + + Dlx dlx; + for (auto const& [allergen, ingredients] : Arrange(input)) { + auto 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; + dlx.Set(row_id, a_id); + dlx.Set(row_id, a_ids.size() + i_id); + } + } + + // ingredients don't have to be assigned to anything + for (auto const& [i,n] : i_ids) { + 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()]); + } + + // build comma-separated list of ingredients + std::string result; + for (auto const & [_,i] : entries) { + result += i; + result += ','; + } + result.pop_back(); + return result; +} + +} // namespace + +TEST_SUITE("documented examples") { + +std::istringstream in { + "mxmxvkd kfcds sqjhc nhms (contains dairy, fish)\n" + "trh fvjkl sbzzf mxmxvkd (contains dairy)\n" + "sqjhc fvjkl (contains soy)\n" + "sqjhc mxmxvkd sbzzf (contains fish)\n" +}; +auto input = Parse(in); + +TEST_CASE("parser") { + Input expected { + {{"mxmxvkd", "kfcds", "sqjhc", "nhms"}, {"dairy", "fish"}}, + {{"trh", "fvjkl", "sbzzf", "mxmxvkd"}, {"dairy"}}, + {{"sqjhc", "fvjkl"}, {"soy"}}, + {{"sqjhc", "mxmxvkd", "sbzzf"}, {"fish"}} + }; + REQUIRE(input == expected); +} + +TEST_CASE("part 1") { + REQUIRE(Part1(input) == 5); +} + +TEST_CASE("part 2") { + REQUIRE(Part2(input) == "mxmxvkd,sqjhc,fvjkl"); +} + +} + +auto main(int argc, char** argv) -> int { + auto input = Parse(Startup(argc, argv)); + std::cout << "Part 1: " << Part1(input) << std::endl; + std::cout << "Part 2: " << Part2(input) << std::endl; +} diff --git a/2020/CMakeLists.txt b/2020/CMakeLists.txt index 697d576..752953b 100644 --- a/2020/CMakeLists.txt +++ b/2020/CMakeLists.txt @@ -2,4 +2,7 @@ add_executable(2020_02 02.cpp) target_link_libraries(2020_02 aocpp) add_executable(2020_03 03.cpp) -target_link_libraries(2020_03 aocpp) \ No newline at end of file +target_link_libraries(2020_03 aocpp) + +add_executable(2020_21 21.cpp) +target_link_libraries(2020_21 aocpp dlx) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ad2f2f9..b896419 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ find_package(PkgConfig) pkg_check_modules(GMP REQUIRED IMPORTED_TARGET gmpxx) add_subdirectory(lib) +add_subdirectory(dlx) add_subdirectory(zmod) add_subdirectory(intcode) add_subdirectory(2017) diff --git a/dlx/CMakeLists.txt b/dlx/CMakeLists.txt new file mode 100644 index 0000000..085e23f --- /dev/null +++ b/dlx/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(dlx src/dlx.cpp) +target_include_directories(dlx PUBLIC include) \ No newline at end of file diff --git a/dlx/include/dlx.hpp b/dlx/include/dlx.hpp new file mode 100644 index 0000000..24876a2 --- /dev/null +++ b/dlx/include/dlx.hpp @@ -0,0 +1,113 @@ +#ifndef DLX_DLX_HPP_ +#define DLX_DLX_HPP_ + +#include +#include +#include + +namespace dlx { + +class Dlx { + + struct Cell { + Cell *U, *D, *L, *R; + std::size_t n; + union { + Cell* c; + std::size_t s; + }; + + auto LR_self() -> void { L = R = this; } + auto UD_self() -> void { U = D = this; } + auto LR_delete() -> void { L->R = R; R->L = L; } + auto UD_delete() -> void { U->D = D; D->U = U; } + auto UD_restore() -> void { U->D = D->U = this; } + auto LR_restore() -> void { L->R = R->L = this; } + auto LR_insert(Cell* k) -> void { L = k->L; R = k; k->L = k->L->R = this; } + auto UD_insert(Cell* k) -> void { U = k->U, D = k, k->U = k->U->D = this; } + + static Cell* ColNew() { + auto c = new Cell; + c->UD_self(); + c->s = 0; + return c; + } + + auto CoverCol() -> void { + LR_delete(); + for (auto i = D; i != this; i = i->D) { + for (auto j = i->R; j != i; j = j->R) { + j->UD_delete(); + j->c->s--; + } + } + } + + auto UncoverCol() -> void { + for (auto i = U; i != this; i = i->U) { + for (auto j = i->L; j != i; j = j->L) { + j->c->s++; + j->UD_restore(); + } + } + LR_restore(); + } + + }; + + std::vector ctab_; + std::vector rtab_; + Cell* root_; + + auto AddCol() -> void; + auto AddRow() -> void; + auto AllocCol(std::size_t n) -> void; + auto AllocRow(std::size_t n) -> void; + +public: + Dlx(); + ~Dlx(); + + // Returns number of rows. + auto Rows() const -> std::size_t; + + // Returns number of columns. + auto Cols() const -> std::size_t; + + // Places a 1 in the given row and column. + // 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. + auto MarkOptional(std::size_t col) -> void; + + // Removes a row from consideration. Returns 0 on success, -1 otherwise. + // Should only be called after all dlx_set() calls. + auto RemoveRow(std::size_t row) -> int; + + // Picks a row to be part of the solution. Returns 0 on success, -1 otherwise. + // Should only be called after all dlx_set() calls and dlx_remove_row() calls. + // TODO: Check the row can be legally chosen. + auto PickRow(std::size_t row) -> int; + + auto Solve( + 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){} + ); +} + +} // namespace + +#endif \ No newline at end of file diff --git a/dlx/src/dlx.cpp b/dlx/src/dlx.cpp new file mode 100644 index 0000000..651afcd --- /dev/null +++ b/dlx/src/dlx.cpp @@ -0,0 +1,170 @@ +// See http://en.wikipedia.org/wiki/Dancing_Links. + +#include "dlx.hpp" + +#include + +using namespace dlx; + +#define F(i,n) for(int i = 0; i < n; i++) + +#define C(i,n,dir) for(cell_ptr i = (n)->dir; i != n; i = i->dir) + +Dlx::Dlx() { + root_ = Cell::ColNew(); + root_->LR_self(); +} + +Dlx::~Dlx() { + for (auto const row : rtab_) { + if (row) { + Cell* next; + for (auto cursor = row->R; cursor != row; cursor = next) { + next = cursor->R; + delete cursor; + } + delete row; + } + } + for (auto const col : ctab_) { + if (col) { delete col; } + } + delete root_; +} + +auto Dlx::Rows() const -> std::size_t { return rtab_.size(); } +auto Dlx::Cols() const -> std::size_t { return ctab_.size(); } + +auto Dlx::AddCol() -> void { + auto c = Cell::ColNew(); + c->LR_insert(root_); + c->n = Cols(); + ctab_.push_back(c); +} + +auto Dlx::AddRow() -> void { + rtab_.push_back(nullptr); +} +auto Dlx::AllocCol(std::size_t n) -> void { + while(Cols() <= n) AddCol(); +} + +auto Dlx::AllocRow(std::size_t n) -> void { + while(Rows() <= n) AddRow(); +} + + +auto Dlx::MarkOptional(std::size_t col) -> void { + AllocCol(col); + auto c = ctab_[col]; + // Prevent undeletion by self-linking. + c->LR_delete(); + c->LR_self(); +} + + +auto Dlx::Set(std::size_t row, std::size_t col) -> void { + // We don't bother sorting. DLX works fine with jumbled rows and columns. + // We just have to watch out for duplicates. (Actually, I think the DLX code + // works even with duplicates, though it would be inefficient.) + // + // For a given column, the UD list is ordered in the order that dlx_set() + // is called, not by row number. Similarly for a given row and its LR list. + AllocRow(row); + AllocCol(col); + auto c = ctab_[col]; + + auto const new1 = [&]() -> Cell* { + auto n = new Cell; + n->n = row; + n->c = c; + c->s++; + n->UD_insert(c); + return n; + }; + + auto & r = rtab_[row]; + + if (!r) { + r = new1(); + r->LR_self(); + return; + } + + // Ignore duplicates. + if (r->c->n == col) return; + + for (auto cursor = r->R; cursor != r; cursor = cursor->R) { + if (cursor->c->n == col) return; + } + + // Otherwise insert at end of LR list. + new1()->LR_insert(r); +} + +auto Dlx::PickRow(std::size_t i) -> int { + auto r = rtab_.at(i); + if (!r) return 0; // Empty row. + r->c->CoverCol(); + for (auto j = r->R; j != r; j = j->R) { + j->c->CoverCol(); + } + + return 0; +} + + +auto Dlx::RemoveRow(std::size_t i) -> int { + auto & r = rtab_.at(i); + if (!r) return 0; // Empty row. + r->UD_delete(); + r->c->s--; + for (auto j = r->R; j != r; j = j->R) { + j->UD_delete(); + j->c->s--; + } + r = nullptr; + return 0; +} + +auto Dlx::Solve( + 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 c = root_->R; + if (c == root_) { + found_cb(); + return; + } + + auto s = std::numeric_limits::max(); // S-heuristic: choose first most-constrained column. + for (auto i = root_->R; i != root_; i = i->R) { + if (i->s < s) { + s = (c = i)->s; + } + } + + if (!s) { + stuck_cb(c->n); + return; + } + + c->CoverCol(); + for (auto r = c->D; r != c; r = r->D) { + try_cb(c->n, s, r->n); + for (auto j = r->R; j != r; j = j->R) { + j->c->CoverCol(); + } + self(self); + undo_cb(); + for (auto j = r->L; j != r; j=j->L) { + j->c->UncoverCol(); + } + } + c->UncoverCol(); + }; + recurse(recurse); +}