import Foundation let input = loadData(day: 21) let scanner = Scanner(string: input) scanner.charactersToBeSkipped = .whitespaces extension Scanner { func ingredient() -> String? { scanUpToCharacters(from: .whitespaces) } func allergens() -> Set? { guard string("(contains") else { return nil } var result: Set = [] let end = CharacterSet(charactersIn: ",)") repeat { guard let allergen = scanUpToCharacters(from: end) else { return nil } result.insert(allergen) } while string(",") guard string(")") else { return nil } return result } func end() -> Set? { let allergens = self.allergens() guard string("\n") else { return nil } return allergens ?? [] } typealias Food = (ingredients: Set, allergens: Set) func food() -> Food? { var ingredients = Set() while true { guard let ingredient = self.ingredient() else { return nil } ingredients.insert(ingredient) if let end = self.end() { return (ingredients, end) } } } func foods() -> [Food] { var result: [Food] = [] while !isAtEnd, let food = self.food() { result.append(food) } return result } } let data = scanner.foods() var possibleIngredientsByAllergen: [String: Set] = [:] var allIngredients: Set = [] var allergens: [(String, String)] = [] for (ingredients, allergens) in data { allIngredients.formUnion(ingredients) for allergen in allergens { if let possible = possibleIngredientsByAllergen[allergen] { possibleIngredientsByAllergen[allergen] = possible.intersection(ingredients) } else { possibleIngredientsByAllergen[allergen] = ingredients } } } while !possibleIngredientsByAllergen.isEmpty { for (allergen, possible) in possibleIngredientsByAllergen where possible.count == 1 { let ingredient = possible.first! allIngredients.remove(ingredient) possibleIngredientsByAllergen.removeValue(forKey: allergen) allergens.append((allergen, ingredient)) for key in possibleIngredientsByAllergen.keys { possibleIngredientsByAllergen[key]?.remove(ingredient) } } } let count = data.reduce(0) { $0 + $1.ingredients.intersection(allIngredients).count } print("part 1:", count) let canonicalDangerousList = allergens .sorted { $0.0 < $1.0 } .map { $0.1 } .joined(separator: ",") print("part 2:", canonicalDangerousList)