— Day 14: Extended Polymerization —
Section
defmodule Setup do
def get_input(prompt) do
case IO.gets(prompt) do
:eof -> ""
line -> line <> get_input(prompt)
end
end
def parse(binary) do
binary
|> String.split("\n\n", trim: true)
|> then(fn [template, rules] ->
{String.to_charlist(template),
rules
|> String.split([" -> ", "\n"], trim: true)
|> Enum.chunk_every(2)
|> Map.new(fn [<<a, b>>, <<c>>] -> {[a, b], c} end)}
end)
end
end
{template, rules} =
Setup.get_input("input")
|> Setup.parse()
defmodule Poly do
def grow(pair_counts, rules) do
# given the pair count of the previous template we calculate the new pair counts
# according to the given polymere grow rules
Enum.reduce(pair_counts, %{}, fn {[a, b] = pair, count}, pair_counts ->
# each pair generates two new pairs, discarding itself:
# AB -> C generates AC and CB for every AB-pair in the template
#
# NOTE: different pairs in the template can generate
# the same pairs in the next growth step
pair_counts
|> Map.update([a, rules[pair]], count, &(&1 + count))
|> Map.update([rules[pair], b], count, &(&1 + count))
end)
end
defp pairs(template) do
# given a template generate all pairs in that template
# ABCD -> [AB, BC, CD]
template |> Enum.chunk_every(2, 1, :discard) |> Enum.frequencies()
end
def solve(template, rules, steps) do
Enum.reduce(1..steps, pairs(template), fn _step, acc -> grow(acc, rules) end)
# It returns the pair count after N steps
#
# Now we do some counting on the elements
|> Enum.reduce(%{}, fn {[a, b], count}, acc ->
# Create a map with counts for each element
# Each pair adds 2*N elements to the list where N is the number
# of occurances of the pair.
acc
|> Map.update(a, count, &(&1 + count))
|> Map.update(b, count, &(&1 + count))
end)
# All elements are counted twice as they appear in two pairs
# AB BC CD ---> 2xB, 2xC, but the edges are counted once
# we correct that here:
|> Map.update!(List.first(template), &(&1 + 1))
|> Map.update!(List.last(template), &(&1 + 1))
# we're not interested in the letters anymore
|> Map.values()
# we only want to know the min/max values
|> Enum.min_max()
|> then(fn {min, max} ->
# substract, and correct for the double counting
div(max - min, 2)
end)
end
end
Part1
Poly.solve(template, rules, 10)
Part2
Poly.solve(template, rules, 40)