2020-21
This commit is contained in:
parent
1d583ed6e4
commit
eac45695b5
197
2020/21.cpp
Normal file
197
2020/21.cpp
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <tuple>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <numeric>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <doctest.h>
|
||||||
|
|
||||||
|
#include <aocpp/Startup.hpp>
|
||||||
|
#include <dlx.hpp>
|
||||||
|
|
||||||
|
using aocpp::Startup;
|
||||||
|
using dlx::Dlx;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using Input = std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>>;
|
||||||
|
|
||||||
|
auto SplitOn(std::string stuff, std::string sep) -> std::vector<std::string> {
|
||||||
|
std::vector<std::string> 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::string, std::vector<std::string>>
|
||||||
|
{
|
||||||
|
std::map<std::string, std::vector<std::string>> 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<std::string> 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<std::string, std::vector<std::string>> possible = Arrange(input);
|
||||||
|
std::map<std::string, std::size_t> 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<std::string, std::size_t> a_ids;
|
||||||
|
std::map<std::string, std::size_t> i_ids;
|
||||||
|
std::vector<std::string> id_to_a;
|
||||||
|
std::vector<std::string> 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<std::size_t> solution;
|
||||||
|
dlx::ForallCover(dlx, [&](auto & sln){ solution = sln; });
|
||||||
|
|
||||||
|
// extract assignment map from dlx row ids
|
||||||
|
std::map<std::string, std::string> 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;
|
||||||
|
}
|
|
@ -3,3 +3,6 @@ target_link_libraries(2020_02 aocpp)
|
||||||
|
|
||||||
add_executable(2020_03 03.cpp)
|
add_executable(2020_03 03.cpp)
|
||||||
target_link_libraries(2020_03 aocpp)
|
target_link_libraries(2020_03 aocpp)
|
||||||
|
|
||||||
|
add_executable(2020_21 21.cpp)
|
||||||
|
target_link_libraries(2020_21 aocpp dlx)
|
|
@ -20,6 +20,7 @@ find_package(PkgConfig)
|
||||||
pkg_check_modules(GMP REQUIRED IMPORTED_TARGET gmpxx)
|
pkg_check_modules(GMP REQUIRED IMPORTED_TARGET gmpxx)
|
||||||
|
|
||||||
add_subdirectory(lib)
|
add_subdirectory(lib)
|
||||||
|
add_subdirectory(dlx)
|
||||||
add_subdirectory(zmod)
|
add_subdirectory(zmod)
|
||||||
add_subdirectory(intcode)
|
add_subdirectory(intcode)
|
||||||
add_subdirectory(2017)
|
add_subdirectory(2017)
|
||||||
|
|
2
dlx/CMakeLists.txt
Normal file
2
dlx/CMakeLists.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
add_library(dlx src/dlx.cpp)
|
||||||
|
target_include_directories(dlx PUBLIC include)
|
113
dlx/include/dlx.hpp
Normal file
113
dlx/include/dlx.hpp
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#ifndef DLX_DLX_HPP_
|
||||||
|
#define DLX_DLX_HPP_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<Cell*> ctab_;
|
||||||
|
std::vector<Cell*> 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<void(std::size_t, std::size_t, std::size_t)> try_cb,
|
||||||
|
std::function<void()> undo_cb,
|
||||||
|
std::function<void()> found_cb,
|
||||||
|
std::function<void(std::size_t)> stuck_cb) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ForallCover(Dlx& dlx, auto cb) {
|
||||||
|
std::vector<std::size_t> 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
|
170
dlx/src/dlx.cpp
Normal file
170
dlx/src/dlx.cpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// See http://en.wikipedia.org/wiki/Dancing_Links.
|
||||||
|
|
||||||
|
#include "dlx.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
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<void(std::size_t, std::size_t, std::size_t)> try_cb,
|
||||||
|
std::function<void()> undo_cb,
|
||||||
|
std::function<void()> found_cb,
|
||||||
|
std::function<void(std::size_t)> 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<std::size_t>::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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user