2020-12-21 - Allergen Assessment
(original .ipynb)
Day 21 input is a sequence of ingredients in an unfamiliar language followed by a list of allergens (mine is here). Each allergen maps to a single ingredient. Part 1 involves finding the number of appearances of each ingredient which is not an allergen. Part 2 involves finding the allergen/ingredient mappings and producing a comma-separated string of ingredients sorted by the alphabetic order of their allergen.
from functools import reduce
from collections import defaultdict
puzzle_input_str = open("puzzle_input/day21.txt", "r").read()
test_input_str = """mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
trh fvjkl sbzzf mxmxvkd (contains dairy)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)"""
def parse_food(line):
ingredients_raw, allergens_raw = line.split("(contains")
ingredients = ingredients_raw.strip().split(" ")
allergens = allergens_raw[:-1].strip().split(", ")
return set(ingredients), set(allergens)
def ingredients_for_allergen(allergen, ingredients_by_allergen):
ingredients = reduce(lambda a,b: a.intersection(b), ingredients_by_allergen[allergen])
return set(ingredients)
def process_foods(foods):
ingredients_by_allergen = defaultdict(list)
ingredient_appearances = defaultdict(lambda:0)
for ingredients, allergens in foods:
for ingredient in ingredients:
ingredient_appearances[ingredient] += 1
for allergen in allergens:
ingredients_by_allergen[allergen].append(set(ingredients))
return ingredients_by_allergen, ingredient_appearances
def part1(input_str):
input_lines = input_str.split("\n")
foods = [parse_food(line) for line in input_lines] # lmao did this mf just say "foods"
ingredients_by_allergen, ingredient_appearances = process_foods(foods)
maybe_contains_allergen = set()
for allergen in ingredients_by_allergen:
maybe_contains_allergen.update(ingredients_for_allergen(allergen, ingredients_by_allergen))
total = 0
for ingredient in ingredient_appearances:
if not (ingredient in maybe_contains_allergen):
total += ingredient_appearances[ingredient]
return total
assert 5 == part1(test_input_str)
part1(puzzle_input_str)
2635
def part2(input_str):
input_lines = input_str.split("\n")
foods = [parse_food(line) for line in input_lines]
ingredients_by_allergen, ingredient_appearances = process_foods(foods)
allergen_mapping = {}
while ingredients_by_allergen.keys():
newly_identified_allergens = {}
for allergen in ingredients_by_allergen:
ingredient_candidates = reduce(
lambda a,b: a.intersection(b),
ingredients_by_allergen[allergen]
)
if len(ingredient_candidates) == 1:
ingredient = ingredient_candidates.pop()
newly_identified_allergens[allergen] = ingredient
for allergen in newly_identified_allergens:
allergen_mapping[allergen] = newly_identified_allergens[allergen]
del ingredients_by_allergen[allergen]
for allergen in ingredients_by_allergen:
new_foods = []
for food in ingredients_by_allergen[allergen]:
new_ingredients = set()
for ingredient in food:
if not (ingredient in newly_identified_allergens.values()):
new_ingredients.add(ingredient)
new_foods.append(new_ingredients)
ingredients_by_allergen[allergen] = new_foods
sorted_allergens = sorted(allergen_mapping.keys())
return ",".join([allergen_mapping[allergen] for allergen in sorted_allergens])
assert "mxmxvkd,sqjhc,fvjkl" == part2(test_input_str)
print(part2(puzzle_input_str))
xncgqbcp,frkmp,qhqs,qnhjhn,dhsnxr,rzrktx,ntflq,lgnhmx