Advent of Code 2020 in Elixir - Day 7

Handy Haversacks

defmodule Aoc2020.Day7 do
  @moduledoc "Handy Haversacks"

  def run(), do: parse_input() |> part_2()

  def part_1(rules) do
    find_parents(rules, :"shiny gold") |> length
  end

  def part_2(rules) do
    count_children(rules, :"shiny gold")
    |> List.flatten()
    |> Enum.sum()
  end

  def parse_input() do
    File.read!("priv/inputs/2020/day7.txt")
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(&parse_rule/1)
  end

  def parse_rule(line) do
    [colour, children] = String.split(line, " contain ")
    {String.to_atom(String.replace(colour, " bags", "")), parse_children(children)}
  end

  def parse_children(str) do
    str
    |> String.split(", ")
    |> Enum.map(&String.replace(&1, ~r( bags?.?), ""))
    |> Enum.map(&parse_count/1)
    |> Enum.filter(& &1)
  end

  def parse_count("no other") do
    nil
  end

  def parse_count(str) do
    [count, colour] = String.split(str, " ", parts: 2)
    {String.to_atom(colour), String.to_integer(count)}
  end

  # part 1
  def find_parents(rules, colour) do
    bag_containers(rules, colour)
    |> List.flatten()
    |> Enum.uniq()
  end

  def bag_containers(rules, colour) do
    has_child(rules, colour)
    |> Enum.map(fn {c, _} -> [c, bag_containers(rules, c)] end)
  end

  def has_child(rules, child_colour) do
    Enum.filter(rules, fn {col, children} -> Keyword.has_key?(children, child_colour) end)
  end

  # part 2
  def count_children(rules, colour, multiplier \\ 1) do
    {_, children} = Enum.find(rules, fn {col, children} -> col == colour end)

    Enum.map(children, fn {k, count} ->
      [count * multiplier, count_children(rules, k, count * multiplier)]
    end)
  end
end