Powered by AppSignal & Oban Pro

Day 5: Supply Stacks

2022/elixir/day-05.livemd

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)