AoC/2020/day21/main.swift

92 lines
2.6 KiB
Swift
Raw Normal View History

2020-12-21 21:41:49 +01:00
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<String>? {
guard string("(contains") else { return nil }
var result: Set<String> = []
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<String>? {
let allergens = self.allergens()
guard string("\n") else { return nil }
return allergens ?? []
}
typealias Food = (ingredients: Set<String>, allergens: Set<String>)
func food() -> Food? {
var ingredients = Set<String>()
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<String>] = [:]
var allIngredients: Set<String> = []
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)