Powered by AppSignal & Oban Pro

Day 14

2021/day-14.livemd

Day 14

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])
example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")
[template | rules] =
  real_input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

rules =
  rules
  |> Enum.map(&String.split(&1, " -> "))
  |> Map.new(fn [k, v] -> {k, v} end)

Part 1

defmodule Polymer do
  def inject(template, rules) do
    pairs =
      template
      |> String.split("", trim: true)
      |> Enum.chunk_every(2, 1)

    inject(pairs, rules, [])
  end

  defp inject([], _, acc), do: acc |> Enum.reverse() |> Enum.join()
  defp inject([[tail] | rest], rules, acc), do: inject(rest, rules, [tail | acc])
  defp inject([[p1, p2] | rest], rules, acc), do: inject(rest, rules, [rules[p1 <> p2], p1 | acc])
end
result =
  1..10
  |> Enum.reduce(template, fn _, acc ->
    Polymer.inject(acc, rules)
  end)

frequencies =
  result
  |> String.split("", trim: true)
  |> Enum.frequencies()

{{_, min}, {_, max}} =
  frequencies
  |> Enum.min_max_by(fn {_, v} -> v end)

max - min

Part 2

template =
  template
  |> String.split("", trim: true)
  |> Enum.chunk_every(2, 1, ["-"])
  |> Enum.frequencies()

polymer =
  Enum.reduce(1..40, template, fn _, polymer ->
    polymer =
      Enum.reduce(polymer, %{}, fn
        {[p1, p2] = polymer, count}, acc ->
          case rules[p1 <> p2] do
            nil ->
              Map.put(acc, polymer, count)

            i ->
              acc
              |> Map.update([p1, i], count, &(&1 + count))
              |> Map.update([i, p2], count, &(&1 + count))
          end
      end)

    polymer
  end)

frequencies =
  polymer
  |> Enum.reduce(%{}, fn {[p1, _], count}, counts ->
    Map.update(counts, p1, count, &(&1 + count))
  end)

{{_, min}, {_, max}} =
  frequencies
  |> Enum.min_max_by(fn {_, v} -> v end)

max - min