|
|
@@ -1,54 +1,44 @@ |
|
|
|
import re |
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
def words(string): |
|
|
|
return frozenset(re.findall(r'\w+', string)) |
|
|
|
|
|
|
|
|
|
|
|
text = sys.stdin.read() |
|
|
|
test = '''mxmxvkd kfcds sqjhc nhms (contains dairy, fish) |
|
|
|
trh fvjkl sbzzf mxmxvkd (contains dairy) |
|
|
|
sqjhc fvjkl (contains soy) |
|
|
|
sqjhc mxmxvkd sbzzf (contains fish)''' |
|
|
|
|
|
|
|
|
|
|
|
contains = {} |
|
|
|
for line in text.splitlines(): |
|
|
|
food, allergens = line[:-1].split(' (contains ') |
|
|
|
food = frozenset(s.strip() for s in food.split(' ')) |
|
|
|
allergens = frozenset(s.strip() for s in allergens.split(',')) |
|
|
|
contains[food] = allergens |
|
|
|
|
|
|
|
all_allergens = {a for b in contains.values() for a in b} |
|
|
|
all_foods = {a for b in contains.keys() for a in b} |
|
|
|
|
|
|
|
reverse = {} |
|
|
|
for food in all_foods: |
|
|
|
reverse[food] = set(all_allergens) |
|
|
|
|
|
|
|
for food, allergens in contains.items(): |
|
|
|
for f2 in reverse: |
|
|
|
if f2 not in food: |
|
|
|
reverse[f2] -= allergens |
|
|
|
|
|
|
|
clean = {k for k, v in reverse.items() if not v} |
|
|
|
ans = 0 |
|
|
|
for v in contains.keys(): |
|
|
|
ans += len(v & clean) |
|
|
|
recipes = dict(map(words, ln.split('contains')) for ln in text.splitlines()) |
|
|
|
all_ingredients = {item for group in recipes.keys() for item in group} |
|
|
|
all_allergens = {item for group in recipes.values() for item in group} |
|
|
|
|
|
|
|
# any ingredient can be any allergen, |
|
|
|
# but if the allergen *is* there, and the ingredient *isn't* |
|
|
|
# then the ingredient *cannot* be the allergen |
|
|
|
options = {ingredient: set(all_allergens) for ingredient in all_ingredients} |
|
|
|
for ingredients, allergens in recipes.items(): |
|
|
|
for f2 in all_ingredients - ingredients: |
|
|
|
options[f2] -= allergens |
|
|
|
|
|
|
|
clean = {ing for ing, als in options.items() if not als} |
|
|
|
ans = sum(len(recipe & clean) for recipe in recipes) |
|
|
|
print(ans) |
|
|
|
|
|
|
|
|
|
|
|
final = {v: k - clean for k, v in contains.items()} |
|
|
|
def recurse(leftover, known=[]): |
|
|
|
if not leftover: |
|
|
|
yield dict(known) |
|
|
|
else: |
|
|
|
ing, = key = min(leftover, key=len) |
|
|
|
for alg in leftover.pop(key): |
|
|
|
a1 = {k - {ing}: v - {alg} for k, v in leftover.items()} |
|
|
|
a2 = known + [(ing, alg)] |
|
|
|
yield from recurse(a1, a2) |
|
|
|
|
|
|
|
|
|
|
|
def recurse(ings, known=tuple()): |
|
|
|
if not ings: |
|
|
|
yield known |
|
|
|
else: |
|
|
|
key, = min(ings, key=lambda k: len(k)) |
|
|
|
for val in sorted(ings.pop(frozenset({key}))): |
|
|
|
expa = known + ((key, val),) |
|
|
|
lob = {k - {key}: v - {val} for k, v in ings.items()} |
|
|
|
yield from recurse(lob, expa) |
|
|
|
def valid(mapping): |
|
|
|
return all(mapping[a] in fs for fs, als in recipes.items() for a in als) |
|
|
|
|
|
|
|
|
|
|
|
for out in recurse(dict(final)): |
|
|
|
d = dict(out) |
|
|
|
if all(({d[e] for e in k} <= set(v)) for k, v in final.items()): |
|
|
|
print(','.join(v for _, v in sorted(out))) |
|
|
|
cleaned_up = {v: k - clean for k, v in recipes.items()} |
|
|
|
maps, = (maps for maps in recurse(cleaned_up) if valid(maps)) |
|
|
|
print(','.join(v for k, v in sorted(maps.items()))) |