Day 19
Mix.install([
{:kino, "~> 0.14.2"}
])
IEx.Helpers.c("/Users/johnb/dev/2015adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input
require Integer
# Note: when making the next template, something like this works well:
# `cat day01.livemd | sed 's/01/02/' > day02.livemd`
#
Installation and Data
input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
(Kino.Input.read(input_source_select) == :example &&
Kino.Input.read(input_p1example)) ||
Kino.Input.read(input_p1puzzleInput)
end
Solution
defmodule Day19 do
@replacement_parser ~r/(\w+) => (\w+)/
def parse_replacements(text) do
text
|> AOC.as_single_lines()
|> Enum.reduce( %{}, fn line, acc ->
[[_, start, replacement]] = Regex.scan(@replacement_parser, line)
{_, acc} = get_and_update_in(acc[start], fn current ->
{current, (current || []) ++ [replacement]}
end)
acc
end)
end
def enumerate_new_molecules(medicine, replacements) do
replacements
|> Enum.reduce(%{}, fn {source, dests}, acc ->
splits = String.split(medicine, source)
dests
|> Enum.reduce(acc, fn dest, acc1 ->
[_left | rest] = Enum.with_index(splits)
rest
|> Enum.reduce(acc1, fn {_split, index}, acc2 ->
prefix = Enum.slice(splits, 0..(index - 1)//1)
|> Enum.join(source)
center = dest # only replace one section at a time
ending = Enum.slice(splits, (index)..-1//1)
|> Enum.join(source)
new_molecule = [prefix, center, ending]
|> Enum.join("")
{_, acc3} = get_and_update_in(acc2[new_molecule], fn current ->
{current, (current || 0) + 1}
end)
acc3
end)
end)
end)
end
def solve1(text) do
[replacements, medicine] = AOC.as_doublespaced_paragraphs(text)
replacements = parse_replacements(replacements)
molecules = enumerate_new_molecules(medicine, replacements)
molecules
|> Map.keys()
|> Enum.count()
end
# %{key => [val1, val2]}
# becomes
# %{val1 => key, val2 => key}
def reverse_replacements(replacements) do
Enum.reduce(replacements, %{}, fn {key, values}, acc ->
Enum.reduce(values, acc, fn value, acc1 ->
put_in(acc1, [value], key)
end)
end)
end
def solve2(text) do
[replacements, medicine] = AOC.as_doublespaced_paragraphs(text)
replacements = parse_replacements(replacements)
reversals = reverse_replacements(replacements)
|> Enum.sort_by(fn {key, _value} -> String.length(key) end, :desc)
|> Enum.map(fn {k, v} -> [k,v] end)
# Iterate thru all the replacements possible, starting at the longest.
Enum.reduce(1..300, {0, medicine}, fn _iteration, {replace_count, new_medicine} ->
reversals
|> Enum.reduce({replace_count, new_medicine}, fn [src, dest], {rep_count2, new_med2} ->
med = String.replace(new_med2, src, dest, global: false)
if med == new_med2 do
{rep_count2, new_med2}
else
{rep_count2 + 1, med}
end
end)
end)
end
end
# Example data:
# e => H
# e => O
# H => HO
# H => OH
# O => HH
#
# HOHOHO
p1data.()
|> Day19.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 7 for HOHOHO)")
# 535
p1data.()
|> Day19.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 6)")
# 212