Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 14: Extended Polymerization

2021/elixir/day-14.livemd

Day 14: Extended Polymerization

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])

Input

input = Kino.Input.textarea("Input")

Parse

defmodule Parser do
  def parse(input) do
    input
    |> String.split("\n\n", trim: true)
    |> then(fn [template, pairs] ->
      pairs
      |> String.split("\n")
      |> Enum.map(fn pair ->
        String.split(pair, " -> ")
        |> List.to_tuple()
      end)
      |> Map.new()
      |> then(fn pairs -> {template, pairs} end)
    end)
  end
end

Part 1

defmodule Solution do
  def step(template, _, 0) do
    template
  end

  def step(template, pairs, steps) do
    step(replace(template, pairs), pairs, steps - 1)
  end

  def replace(template, pairs) do
    template
    |> String.to_charlist()
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(&List.to_string/1)
    |> Enum.map(fn pair ->
      cond do
        (char = Map.get(pairs, pair)) != nil ->
          String.to_charlist(pair)
          |> List.insert_at(1, char)
          |> List.to_string()

        true ->
          pair
      end
    end)
    |> then(fn chunks ->
      for {chunk, index} <- Enum.with_index(chunks) do
        case index do
          0 -> chunk
          _ -> String.slice(chunk, 1..-1)
        end
      end
    end)
    |> List.to_string()
  end
end

Kino.Input.read(input)
|> Parser.parse()
|> then(fn {template, pairs} ->
  Solution.step(template, pairs, 10)
end)
|> then(fn str ->
  str
  |> String.to_charlist()
  |> Enum.frequencies()
end)
|> Enum.min_max_by(&amp;elem(&amp;1, 1))
|> then(fn {{_, min}, {_, max}} -> max - min end)

Part 2

defmodule Solution do
  def step(template, pairs, steps) when is_bitstring(template) do
    template
    |> String.to_charlist()
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(fn pair -> {List.to_string(pair), 1} end)
    |> Enum.group_by(fn {k, _v} -> k end)
    |> Enum.map(fn {k, l} ->
      {k, Enum.map(l, fn {_k, v} -> v end) |> Enum.sum()}
    end)
    |> Map.new()
    |> step(pairs, steps)
  end

  def step(counter, _, 0) do
    counter
  end

  def step(counter, pairs, steps) do
    counter = update(counter, pairs)
    step(counter, pairs, steps - 1)
  end

  def update(counter, pairs) do
    update(counter, counter, pairs)
  end

  def update(_old_counter, new_counter, []) do
    new_counter
  end

  def update(old_counter, new_counter, pairs) do
    [pair | pairs] = pairs
    {key = <>, c} = pair

    new_counter =
      if (count = Map.get(old_counter, key, 0)) > 0 do
        new_counter
        |> inc(key, -count)
        |> inc(List.to_string([l, c]), count)
        |> inc(List.to_string([c, r]), count)
      else
        new_counter
      end

    update(old_counter, new_counter, pairs)
  end

  def inc(counter, key, times) do
    Map.put(counter, key, Map.get(counter, key, 0) + times)
  end
end

Kino.Input.read(input)
|> Parser.parse()
|> then(fn {template, pairs} ->
  Solution.step(template, Map.to_list(pairs), 40)
  |> Enum.group_by(fn {k, _} -> String.at(k, 0) end)
  |> Enum.map(fn {k, list} ->
    {
      k,
      list
      |> Enum.map(fn {_, v} -> v end)
      |> Enum.sum()
    }
  end)
  |> Map.new()
  |> then(fn freq ->
    char = String.last(template)
    Map.put(freq, char, Map.get(freq, char, 0) + 1)
  end)
end)
|> Enum.min_max_by(&amp;elem(&amp;1, 1))
|> then(fn {{_, min}, {_, max}} -> max - min end)