Powered by AppSignal & Oban Pro

Day 14: Extended Polymerization

day_14_extended_polymerization.livemd

Day 14: Extended Polymerization

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Setup

[polymer_template_input | pair_insertion_rule_input] =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

polymer_template = polymer_template_input |> String.graphemes()

pair_insertion_rules =
  pair_insertion_rule_input
  |> Enum.map(fn <> <> " -> " <> <> ->
    [[e1, e2], e]
  end)

{polymer_template, pair_insertion_rules}

Part 1

https://adventofcode.com/2021/day/14

Solve: Part 1

defmodule Day14.Part1 do
  def run(polymer_template, step) do
    do_run(polymer_template, step)
  end

  def answer(polymer) do
    {{_, min_frequency}, {_, max_frequency}} =
      polymer
      |> Enum.frequencies()
      |> Enum.min_max_by(fn {_e, frequency} -> frequency end)

    max_frequency - min_frequency
  end

  defp do_run(polymer, _remain_step = 0) do
    polymer
  end

  defp do_run(polymer, remain_step) do
    new_polymer =
      polymer
      |> Enum.chunk_every(2, 1)
      |> Enum.reduce([], fn
        [e1, e2], new_polymer -> insert(e1, e2, new_polymer)
        [e_last], new_polymer -> [e_last | new_polymer]
      end)
      |> Enum.reverse()

    do_run(new_polymer, remain_step - 1)
  end

  pair_insertion_rules
  |> Enum.map(fn [[e1, e2], e] ->
    def insert(unquote(e1), unquote(e2), polymer) do
      [unquote(e), unquote(e1) | polymer]
    end
  end)
end

Day14.Part1.run(polymer_template, 40)
|> Polymerizer.answer()

Part 2

https://adventofcode.com/2021/day/14#part2

Solve: Part 2

defmodule Day14.Part2 do
  def run(polymer_template, step) do
    pair_count_map =
      polymer_template
      |> Enum.chunk_every(2, 1, :discard)
      |> Enum.frequencies()

    do_run(pair_count_map, step)
  end

  def answer(polymer_template, pair_count_map) do
    first_e = polymer_template |> List.first()
    last_e = polymer_template |> List.last()

    {{min_e, min_frequency}, {max_e, max_frequency}} =
      pair_count_map
      |> Enum.reduce(%{}, fn {[e1, e2], count}, frequncies ->
        frequncies
        |> Map.update(e1, count, &amp;(&amp;1 + count))
        |> Map.update(e2, count, &amp;(&amp;1 + count))
      end)
      |> Enum.min_max_by(fn {_e, frequency} -> frequency end)

    min_frequency =
      case min_e in [first_e, last_e] do
        true -> min_frequency + 1
        false -> min_frequency
      end

    max_frequency =
      case max_e in [first_e, last_e] do
        true -> max_frequency + 1
        false -> max_frequency
      end

    div(max_frequency - min_frequency, 2)
  end

  defp do_run(pair_count_map, _remain_step = 0) do
    pair_count_map
  end

  defp do_run(pair_count_map, remain_step) do
    new_pair_count_map =
      pair_count_map
      |> Enum.reduce(%{}, fn {[e1, e2], count}, new_pair_count_map ->
        new_pair_count_map
        |> insert_pairs([e1, e2], count)
      end)

    do_run(new_pair_count_map, remain_step - 1)
  end

  pair_insertion_rules
  |> Enum.map(fn [[e1, e2], e] ->
    def insert_pairs(pair_count_map, [unquote(e1), unquote(e2)], count) do
      pair_count_map
      |> Map.update([unquote(e1), unquote(e)], count, &amp;(&amp;1 + count))
      |> Map.update([unquote(e), unquote(e2)], count, &amp;(&amp;1 + count))
    end
  end)
end

Day14.Part2.run(polymer_template, 40)
|> then(fn pair_count_map -> Day14.Part2.answer(polymer_template, pair_count_map) end)