AOC 2022-05 (Supply Stacks)

Mix.install([
  {:kino, "~> 0.7.0"}
])

Common Code

defmodule Transpose do
  def it([[] | _]), do: []

  def it(list) when is_list(list) do
    [Enum.map(list, &hd/1) | it(Enum.map(list, &tl/1))]
  end
end

Input

input = Kino.Input.textarea("Input")
[stacks, instructions] =
  input
  |> Kino.Input.read()
  |> String.split("\n\n")

stacks =
  stacks
  |> String.split("\n")
  |> Enum.map(&String.graphemes/1)
  |> Enum.map(fn row ->
    row
    |> Enum.chunk_every(4)
    |> Enum.map(&Enum.at(&1, 1))
  end)
  |> Enum.drop(-1)
  |> Transpose.it()
  |> Enum.map(fn row ->
    Enum.reject(row, &(&1 == " "))
  end)

instructions =
  instructions
  |> String.split("\n")
  |> Enum.map(&String.split/1)
  |> Enum.map(fn [_move, n, _fromLabel, from, _toLabel, to] ->
    {:move, String.to_integer(n), String.to_integer(from) - 1, String.to_integer(to) - 1}
  end)

Part 1

Find the first crate in each stack after processing all moves.

Enum.reduce(instructions, stacks, fn {:move, n, from, to}, acc ->
  Enum.map_reduce(1..n, acc, fn _x, acc2 ->
    [head | rest] = Enum.at(acc2, from)

    acc2 =
      acc2
      |> List.replace_at(from, rest)
      |> List.replace_at(to, [head | Enum.at(acc2, to)])

    {nil, acc2}
  end)
  |> elem(1)
end)
|> Enum.map(&hd/1)
|> Enum.join()

Part 2

Move stacks, but don't their reverse order while moving.

Enum.reduce(instructions, stacks, fn {:move, n, from, to}, acc ->
  head = Enum.take(Enum.at(acc, from), n)
  rest = Enum.drop(Enum.at(acc, from), n)

  acc
  |> List.replace_at(from, rest)
  |> List.replace_at(to, head ++ Enum.at(acc, to))
end)
|> Enum.map(&hd/1)
|> Enum.join()