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(&elem(&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(&elem(&1, 1))
|> then(fn {{_, min}, {_, max}} -> max - min end)