Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# Day 14 - Polymerization ## Puzzle 1 The incredible pressures at this depth are starting to put a strain on your submarine. The submarine has polymerization equipment that would produce suitable materials to reinforce the submarine, and the nearby volcanically-active caves should even have the necessary input elements in sufficient quantities. The submarine manual contains instructions for finding the optimal polymer formula; specifically, it offers a polymer template and a list of pair insertion rules (your puzzle input). You just need to work out what polymer would result after repeating the pair insertion process a few times. For example: ``` NNCB CH -> B HH -> N CB -> H NH -> C HB -> C HC -> B HN -> C NN -> C BH -> H NC -> B NB -> B BN -> B BB -> N BC -> B CC -> N CN -> C ``` The first line is the polymer template - this is the starting point of the process. The following section defines the pair insertion rules. A rule like AB -> C means that when elements A and B are immediately adjacent, element C should be inserted between them. These insertions all happen simultaneously. So, starting with the polymer template NNCB, the first step simultaneously considers all three pairs: The first pair (NN) matches the rule NN -> C, so element C is inserted between the first N and the second N. The second pair (NC) matches the rule NC -> B, so element B is inserted between the N and the C. The third pair (CB) matches the rule CB -> H, so element H is inserted between the C and the B. Note that these pairs overlap: the second element of one pair is the first element of the next pair. Also, because all pairs are considered simultaneously, inserted elements are not considered to be part of a pair until the next step. After the first step of this process, the polymer becomes NCNBCHB. Here are the results of a few steps using the above rules: ``` Template: NNCB After step 1: NCNBCHB After step 2: NBCCNBBBCBHCB After step 3: NBBBCNCCNBBNBNBBCHBHHBCHB After step 4: NBBNBNBBCCNBCNCCNBBNBBNBBBNBBNBBCBHCBHHNHCBBCBHCB ``` This polymer grows quickly. After step 5, it has length 97; After step 10, it has length 3073. After step 10, B occurs 1749 times, C occurs 298 times, H occurs 161 times, and N occurs 865 times; taking the quantity of the most common element (B, 1749) and subtracting the quantity of the least common element (H, 161) produces 1749 - 161 = 1588. Apply 10 steps of pair insertion to the polymer template and find the most and least common elements in the result. What do you get if you take the quantity of the most common element and subtract the quantity of the least common element? ```elixir sample = """ NNCB CH -> B HH -> N CB -> H NH -> C HB -> C HC -> B HN -> C NN -> C BH -> H NC -> B NB -> B BN -> B BB -> N BC -> B CC -> N CN -> C """ input = File.read!("./input-14.txt") ``` ```elixir defmodule Polymer do defstruct step: 0, polymer: "", rules: %{} def parse(input) do [template | rest] = String.split(input, "\n", trim: true) rules = Enum.reduce(rest, %{}, fn rule, accum -> <<pair::binary-size(2)>> <> " -> " <> insertion = rule Map.put(accum, pair, insertion) end) %__MODULE__{polymer: template, rules: rules} end def puzzle1(%__MODULE__{} = original) do [{_max_char, max} | rest] = original |> apply_steps(10) |> Map.get(:polymer) |> String.to_charlist() |> Enum.frequencies() |> Enum.into([]) |> Enum.sort_by(fn {_char, freq} -> freq end, :desc) [{_min_char, min} | _rest] = Enum.reverse(rest) max - min end def puzzle2(%__MODULE__{} = original) do # TOO SLOW - can we just count the insertions w/o building the polymers? # original |> apply_steps(40) original end def apply_steps(%__MODULE__{step: step} = polymer, step), do: polymer def apply_steps(polymer, step), do: polymer |> apply_step() |> apply_steps(step) def apply_step(%__MODULE__{} = original) do new_polymer = 0..(String.length(original.polymer) - 1) |> Enum.reduce([], fn index, pieces -> pair = String.slice(original.polymer, index, 2) first = String.at(pair, 0) insertion = Map.get(original.rules, pair) if is_nil(insertion) do [first | pieces] else [first <> insertion | pieces] end end) |> Enum.reverse() |> Enum.join("") %{original | step: original.step + 1, polymer: new_polymer} end end sample |> Polymer.parse() |> Polymer.puzzle1() ``` ```elixir input |> Polymer.parse() |> Polymer.puzzle1() ``` ## Part 2 The resulting polymer isn't nearly strong enough to reinforce the submarine. You'll need to run more steps of the pair insertion process; a total of 40 steps should do it. In the above example, the most common element is B (occurring 2192039569602 times) and the least common element is H (occurring 3849876073 times); subtracting these produces 2188189693529. Apply 40 steps of pair insertion to the polymer template and find the most and least common elements in the result. What do you get if you take the quantity of the most common element and subtract the quantity of the least common element? ```elixir %{polymer: polymer, rules: rules} = input |> Polymer.parse() |> IO.inspect() rules = Enum.into(rules, %{}, fn {key, value} -> {String.to_charlist(key), List.first(String.to_charlist(value))} end) freq = polymer |> String.to_charlist() |> Enum.chunk_every(2, 1, :discard) |> Enum.frequencies() char_count = fn pair_frequencies, original -> totals = Enum.reduce(pair_frequencies, %{}, fn {[a, b], pair_count}, counts -> counts |> Map.update(<<a>>, pair_count, fn existing -> existing + pair_count end) |> Map.update(<<b>>, pair_count, fn existing -> existing + pair_count end) end) <<first>> <> _rest = original <<last>> <> _rest = String.reverse(original) totals |> Map.update(<<first>>, 0, fn existing -> existing + 1 end) |> Map.update(<<last>>, 0, fn existing -> existing + 1 end) |> Enum.into(%{}, fn {key, count} -> {key, count / 2} end) end merge_freq = fn freq_one, freq_two -> Enum.reduce(freq_one, freq_two, fn {key, value}, map -> Map.update(map, key, value, fn current -> current + value end) end) end apply_rules = fn frequencies, rules -> rules |> Enum.reduce(%{}, fn {[a, b], insertion}, updates -> case Map.get(frequencies, [a, b]) do nil -> updates freq -> updates |> Map.update([a, b], -freq, fn current -> current - freq end) |> Map.update([a, insertion], freq, fn current -> current + freq end) |> Map.update([insertion, b], freq, fn current -> current + freq end) end end) |> merge_freq.(frequencies) end 0..39 |> Enum.reduce(freq, fn _count, frequencies -> apply_rules.(frequencies, rules) end) |> char_count.(polymer) ``` ```elixir 2_192_039_569_602.0 - 3_849_876_073.0 ``` ```elixir 3_537_050_036_671 - 610_236_657_139 ```
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More