Day 5: Supply Stacks
Mix.install([:kino])
Part 1
input = Kino.Input.textarea("Input", monospace: true)
defmodule Stack do
defstruct l: []
def new() do
%__MODULE__{}
end
def push(stack, nil) do
stack
end
def push(stack, list) when is_list(list) do
%__MODULE__{l: Enum.concat(list, stack.l)}
end
def push(stack, value) do
%__MODULE__{l: [value | stack.l]}
end
def pop(stack) do
{v, l} = List.pop_at(stack.l, 0)
{v, %__MODULE__{l: l}}
end
def pop(stack, n) do
{left, right} = Enum.split(stack.l, n)
{left, %__MODULE__{l: right}}
end
def top(stack) do
List.first(stack.l)
end
end
defmodule Parser do
def parse(input) do
[stacks, procedure] = String.split(input, "\n\n")
{parse_stacks(stacks), parse_procedure(procedure)}
end
defp parse_stacks(str) do
indices =
str
|> String.split("\n")
|> List.last()
|> then(fn s -> Regex.scan(~r/\d+/, s, return: :index) end)
|> Enum.map(fn [{index, _}] -> index end)
stacks =
str
|> String.split("\n")
|> Enum.drop(-1)
|> Enum.reverse()
for {i, n} <- Enum.with_index(indices, 1), s <- stacks, reduce: %{} do
acc ->
char = if String.at(s, i) == " ", do: nil, else: String.at(s, i)
stack = Stack.new() |> Stack.push(char)
Map.update(acc, n, stack, fn stack -> Stack.push(stack, char) end)
end
end
defp parse_procedure(str) do
str
|> String.split("\n")
|> Enum.map(fn s ->
Regex.scan(~r/\d+/, s)
|> Enum.map(fn [n] -> String.to_integer(n) end)
end)
|> Enum.map(fn [amount, from, to] -> %{amount: amount, from: from, to: to} end)
end
end
defmodule Crane9000 do
def run(stacks, procedures) when is_list(procedures) do
for p <- procedures, reduce: stacks do
stacks -> run(stacks, p)
end
end
def run(stacks, procedure) when is_map(procedure) do
for _ <- 1..procedure.amount, reduce: stacks do
stacks -> execute(stacks, procedure.from, procedure.to)
end
end
defp execute(stacks, from, to) do
{stack_from, stack_to} = {stacks[from], stacks[to]}
{val, stack_from} = Stack.pop(stack_from)
stack_to = Stack.push(stack_to, val)
%{stacks | from => stack_from, to => stack_to}
end
end
defmodule M do
def run(input, crane) do
input
|> Kino.Input.read()
|> Parser.parse()
|> then(fn {stacks, procudures} -> crane.run(stacks, procudures) end)
|> Map.to_list()
|> Enum.sort_by(fn {n, _} -> n end)
|> Enum.map(fn {_, stack} -> Stack.top(stack) end)
|> Enum.join("")
end
end
M.run(input, Crane9000)
Part 2
defmodule Crane9001 do
def run(stacks, procedures) when is_list(procedures) do
for p <- procedures, reduce: stacks do
stacks -> run(stacks, p)
end
end
def run(stacks, procedure) when is_map(procedure) do
execute(stacks, procedure.from, procedure.to, procedure.amount)
end
defp execute(stacks, from, to, n) do
{stack_from, stack_to} = {stacks[from], stacks[to]}
{val, stack_from} = Stack.pop(stack_from, n)
stack_to = Stack.push(stack_to, val)
%{stacks | from => stack_from, to => stack_to}
end
end
M.run(input, Crane9001)