Monster Messages
defmodule Aoc2020.Day21 do
@moduledoc "Monster Messages"
def run() do
part_2()
end
def part_1() do
foods = parse_input()
all_ingredients = Enum.flat_map(foods, &elem(&1, 0))
with_allergens =
identify_allergen_ingredients(foods)
|> Enum.reduce(MapSet.new(), fn {_, v}, acc -> MapSet.union(acc, v) end)
no_allergens = MapSet.difference(MapSet.new(all_ingredients), with_allergens)
Enum.count(all_ingredients, fn i -> Enum.member?(no_allergens, i) end)
end
def part_2() do
foods = parse_input()
allergen_ingredients = identify_allergen_ingredients(foods)
match_ingredient_to_allergen(allergen_ingredients, MapSet.new(), %{})
|> Enum.to_list()
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(&elem(&1, 1))
|> Enum.join(",")
end
def parse_input() do
File.read!("priv/inputs/2020/day21.txt")
|> String.trim()
|> String.split("\n")
|> Enum.map(fn line ->
[words, alergens] = String.split(line, ["(contains ", ")"], trim: true)
{MapSet.new(~w/#{words}/), String.split(alergens, ", ")}
end)
end
def identify_allergen_ingredients(foods) do
Enum.reduce(foods, %{}, fn {ingredients, allergens}, acc ->
Enum.reduce(allergens, acc, fn allergen, acc ->
Map.update(acc, allergen, [ingredients], &[ingredients | &1])
end)
end)
|> Enum.map(fn {k, v} -> {k, Enum.reduce(v, &MapSet.intersection/2)} end)
|> Enum.into(%{})
end
def match_ingredient_to_allergen(allergen_ingredients, _, allergen_map)
when map_size(allergen_ingredients) == map_size(allergen_map),
do: allergen_map
def match_ingredient_to_allergen(allergen_ingredients, ingredients_matched, allergen_map) do
{k, v} =
Enum.find(allergen_ingredients, fn {_, v} ->
MapSet.size(MapSet.difference(v, ingredients_matched)) == 1
end)
matched_ingredient = MapSet.difference(v, ingredients_matched) |> MapSet.to_list() |> hd()
match_ingredient_to_allergen(
allergen_ingredients,
MapSet.put(ingredients_matched, matched_ingredient),
Map.put(allergen_map, k, matched_ingredient)
)
end
end