Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


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