79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
from dataclasses import dataclass
|
|
|
|
@dataclass
|
|
class Food:
|
|
ingredients: list[str]
|
|
allergens: list[str]
|
|
|
|
def parse_food(line: str) -> Food:
|
|
delim = line.find(" (contains ")
|
|
return Food(
|
|
ingredients=line[:delim].split(" "),
|
|
allergens=line[delim+11:-2].split(", ")
|
|
)
|
|
|
|
def read_input(filename: str) -> list[Food]:
|
|
with open(filename, "r") as f:
|
|
return list(parse_food(line) for line in f.readlines())
|
|
|
|
def count_ingredient_occurences(foods: list[Food]) -> dict[str, int]:
|
|
ingredient_counts = {}
|
|
for food in foods:
|
|
for ingredient in food.ingredients:
|
|
ingredient_counts[ingredient] = ingredient_counts.get(ingredient, 0) + 1
|
|
return ingredient_counts
|
|
|
|
def get_allergen_map(foods: list[Food]) -> dict[str, set[str]]:
|
|
allergen_map = {}
|
|
for food in foods:
|
|
for allergen in food.allergens:
|
|
if allergen in allergen_map:
|
|
allergen_map[allergen] = allergen_map[allergen]&set(food.ingredients)
|
|
else:
|
|
allergen_map[allergen] = set(food.ingredients)
|
|
return allergen_map
|
|
|
|
def part1(foods: list[Food]) -> int:
|
|
allergen_map = get_allergen_map(foods)
|
|
|
|
allergen_ingredients = set()
|
|
for ingredients in allergen_map.values():
|
|
allergen_ingredients.update(ingredients)
|
|
|
|
count = 0
|
|
ingredient_counts = count_ingredient_occurences(foods)
|
|
for ingredient in ingredient_counts.keys():
|
|
if ingredient not in allergen_ingredients:
|
|
count += ingredient_counts[ingredient]
|
|
|
|
return count
|
|
|
|
def find_with_one_ingredient(allergen_map: dict[str, set[str]]) -> tuple[str, str]|None:
|
|
for allergen, ingredients in allergen_map.items():
|
|
if len(ingredients) == 1:
|
|
# `min` is used just, because you can't use indexing on sets
|
|
return (allergen, min(ingredients))
|
|
|
|
def part2(foods: list[Food]) -> str:
|
|
allergen_map = get_allergen_map(foods)
|
|
allergens = []
|
|
while True:
|
|
pair = find_with_one_ingredient(allergen_map)
|
|
if pair == None:
|
|
break
|
|
allergens.append(pair)
|
|
allergen, ingredient = pair
|
|
del allergen_map[allergen]
|
|
for ingredients in allergen_map.values():
|
|
if ingredient in ingredients:
|
|
ingredients.remove(ingredient)
|
|
|
|
allergens.sort(key=lambda p: p[0])
|
|
|
|
return ",".join(a[1] for a in allergens)
|
|
|
|
if __name__ == "__main__":
|
|
foods = read_input("input.txt")
|
|
print("part1: ", part1(foods))
|
|
print("part2: ", part2(foods))
|