Advent of Code 2020 in Elixir - Day 19

Monster Messages

defmodule Aoc2020.Day19 do
  @moduledoc "Monster Messages"

  def run() do
    part_1()
  end

  def part_1() do
    {rules, messages} = parse_input()
    rule_zero = eval_rule(rules, Keyword.fetch!(rules, :"0"))
    IO.inspect(rule_zero, label: "Rule 0")
    flatten_variants(rule_zero, [[]])
  end

  def part_2() do
    parse_input()
  end

  def parse_input() do
    # File.read!("priv/inputs/2020/day19.txt")
    test_input()
    |> String.trim()
    |> String.split("\n\n", parts: 2)
    |> (fn [rules_str, messages_str] ->
          {parse_rules(rules_str), parse_messages(messages_str)}
        end).()
  end

  def parse_rules(str) do
    str
    |> String.split("\n")
    |> Enum.map(&parse_rule/1)
  end

  def parse_rule(line) do
    [index, sub_rules] = String.split(line, ": ")

    rule =
      case String.contains?(sub_rules, " | ") do
        true -> Enum.map(String.split(sub_rules, " | "), &parse_sub_rule/1)
        false -> parse_sub_rule(sub_rules)
      end

    {String.to_atom(index), rule}
  end

  def parse_sub_rule(<<"\"", letter, "\"">>), do: <<letter>>

  def parse_sub_rule(str) do
    String.split(str, " ") |> Enum.map(&String.to_atom/1)
  end

  def parse_messages(str), do: String.split(str, "\n")

  def test_input() do
    "0: 4 1 5\n1: 2 3 | 3 2\n2: 4 4 | 5 5\n3: 4 5 | 5 4\n4: \"a\"\n5: \"b\"\n\nababbb\nbababa\nabbbab\naaabbb\naaaabbb"
  end

  def eval_rule(_, []), do: []

  def eval_rule(rules, [head | rest]) do
    case Keyword.fetch!(rules, head) do
      letter when is_binary(letter) -> [letter | eval_rule(rules, rest)]
      lst when is_list(lst) -> [Enum.map(lst, &eval_rule(rules, &1)) | eval_rule(rules, rest)]
    end
  end

  def flatten_variants([], acc), do: Enum.map(acc, &Enum.reverse/1)

  def flatten_variants([value | rest], acc) when is_binary(value) do
    Enum.map(acc, &[flatten_variants(rest, acc) | [value | &1]])
  end

  def flatten_variants([[branchA, branchB] | rest], acc) do
    IO.inspect([branchA | rest], label: "Branch A")

    Enum.map(acc, &flatten_variants([branchA | rest], &1)) ++
      Enum.map(acc, &flatten_variants([branchB | rest], &1))
  end
end