#include #include #include #include #include #include #include #include #include #include // Pair of ingredients and allergens using IngredientLineItem = std::pair, std::vector>; auto constexpr INGREDIENT_PATTERN = R"((.*) \(contains (.*)\))"; std::vector readInput(const std::string &filename) { std::vector input; std::string line; std::ifstream file(filename); while (std::getline(file, line)) { input.push_back(line); } return input; } /** * Split an input line into its components of both the foreign ingredient and the allergens * @param inputLine The input line * @return std::pair The ingredients and allergens as a pair */ std::pair splitInputLine(const std::string &inputLine) { std::regex pattern(INGREDIENT_PATTERN); std::smatch matches; if (!std::regex_match(inputLine, matches, pattern)) { throw std::invalid_argument("Invalid input line"); } return std::make_pair(matches[1].str(), matches[2].str()); } /** * Parse the puzzle input * @param input The puzzle input * @return std::vector The line items for the ingredients */ std::vector parseInput(const std::vector &input) { std::vector res; std::transform(input.cbegin(), input.cend(), std::back_inserter(res), [](const std::string &inputLine) { auto lineComponents = splitInputLine(inputLine); std::vector unknownIngredients; std::vector allergens; folly::split(" ", lineComponents.first, unknownIngredients); folly::split(", ", lineComponents.second, allergens); return std::make_pair(unknownIngredients, allergens); }); return res; } /** * Correlate ingredients to their all * @param input The parsed puzzle input * @return std::map> A map of allergens to their possible ingredients (not concrete) */ std::map> correlateIngredients(const std::vector &input) { std::map> knownCorrelations; std::vector allIngredients; for (const IngredientLineItem &inputLine : input) { for (const std::string &allergen : inputLine.second) { std::set &allergenCorrelations = knownCorrelations[allergen]; std::set allergenMatches(inputLine.first.cbegin(), inputLine.first.cend()); if (allergenCorrelations.empty()) { allergenCorrelations = std::move(allergenMatches); continue; } std::set intersection; std::set_intersection( allergenCorrelations.cbegin(), allergenCorrelations.cend(), allergenMatches.cbegin(), allergenMatches.cend(), std::inserter(intersection, intersection.end())); allergenCorrelations = std::move(intersection); } } return knownCorrelations; } /** * Given a map of the known allergen mappings, generate the canonical name * @param mappedIngredients The mapped ingredients * @return std::string The canonical name of the food */ std::string generateCanonicalName(const std::map &mappedIngredients) { std::vector sortedAllergens; std::transform( mappedIngredients.cbegin(), mappedIngredients.cend(), std::back_inserter(sortedAllergens), [](const auto &entry) { return entry.first; }); std::sort(sortedAllergens.begin(), sortedAllergens.end()); std::vector finalNameComponents; std::transform( sortedAllergens.cbegin(), sortedAllergens.cend(), std::back_inserter(finalNameComponents), [&mappedIngredients](const std::string &allergenName) { return mappedIngredients.at(allergenName); }); return folly::join(",", finalNameComponents); } int part1(const std::vector &input) { auto knownCorrelations = correlateIngredients(input); std::set foundIngredients; for (const auto &correlationEntry : knownCorrelations) { foundIngredients.insert(correlationEntry.second.cbegin(), correlationEntry.second.cend()); } std::vector allIngredients; for (const IngredientLineItem &item : input) { allIngredients.insert(allIngredients.end(), item.first.cbegin(), item.first.cend()); } std::set allIngredientsSet(allIngredients.cbegin(), allIngredients.cend()); std::set unmatchedIngredients; std::set_difference( allIngredientsSet.cbegin(), allIngredientsSet.cend(), foundIngredients.cbegin(), foundIngredients.cend(), std::inserter(unmatchedIngredients, unmatchedIngredients.end())); return std::count_if( allIngredients.begin(), allIngredients.end(), [&unmatchedIngredients](const std::string &ingredient) { return unmatchedIngredients.find(ingredient) != unmatchedIngredients.end(); }); } std::string part2(const std::vector &input) { auto knownCorrelations = correlateIngredients(input); // Prioritize the sets we know about based on the size of the containers they point to auto compareSizes = [&knownCorrelations](const std::string &allergen1, const std::string &allergen2) { const auto &correlationSet1 = knownCorrelations.at(allergen1); const auto &correlationSet2 = knownCorrelations.at(allergen2); return correlationSet1.size() < correlationSet2.size(); }; std::priority_queue, decltype(compareSizes)> toVisit(compareSizes); std::for_each(knownCorrelations.cbegin(), knownCorrelations.cend(), [&toVisit](const auto &entry) { toVisit.push(entry.first); }); // toRevisit is used so we don't continually go back to the same set that happens to be the same size over and over // again std::vector toRevisit; std::map mappedIngredients; std::set usedIngredients; int lastSize = 0; while (!toVisit.empty() || !toRevisit.empty()) { std::optional allergenName; std::optional>> correlationSet; // We can't get the allergen name if the visit set is empty if (!toVisit.empty()) { allergenName = toVisit.top(); correlationSet = knownCorrelations.at(*allergenName); } if (toVisit.empty() || (correlationSet->get().size() > lastSize && !toRevisit.empty())) { // priority_queue does not provide a ranged insert - do this by hand. std::for_each(toRevisit.begin(), toRevisit.end(), [&toVisit](const std::string set) { toVisit.push(set); }); toRevisit.clear(); continue; } // This pop is safe - we know that the set must have at least one element at this point toVisit.pop(); lastSize = correlationSet->get().size(); std::set ingredientSet; std::set_difference( correlationSet->get().cbegin(), correlationSet->get().cend(), usedIngredients.cbegin(), usedIngredients.cend(), std::inserter(ingredientSet, ingredientSet.end())); // If there's only one element, we know what the allergen correlates to, so we're done if (ingredientSet.size() == 1) { std::string ingredientName = *ingredientSet.begin(); usedIngredients.insert(ingredientName); mappedIngredients.emplace(*allergenName, ingredientName); } else { // If there's more than one, save the updated set, and move on knownCorrelations[*allergenName] = ingredientSet; toRevisit.push_back(*allergenName); } } return generateCanonicalName(mappedIngredients); } int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << argv[0] << " " << std::endl; return 1; } auto input = readInput(argv[1]); auto parsedInput = parseInput(input); std::cout << part1(parsedInput) << std::endl; std::cout << part2(parsedInput) << std::endl; }