Advent of Code 2020 in Elixir - Day 21

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