#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; }