Powered by AppSignal & Oban Pro

Day 05

2022/day05.livemd

Day 05

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

Section

defmodule Day05 do
  def part1(input_path) do
    {stacks, instructions} = parse_input(input_path)

    instructions
    |> Enum.reduce(stacks, fn instruction, stacks ->
      move1(stacks, instruction.from, instruction.to, instruction.amount)
    end)
    |> Enum.sort()
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&hd/1)
  end

  def part2(input_path) do
    {stacks, instructions} = parse_input(input_path)

    instructions
    |> Enum.reduce(stacks, fn instruction, stacks ->
      move2(stacks, instruction.from, instruction.to, instruction.amount)
    end)
    |> Enum.sort()
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&hd/1)
  end

  defp parse_input(input_path) do
    [stacks_part, instructions_part] =
      input_path
      |> File.read!()
      |> String.split("\n\n", parts: 2, trim: true)

    {build_stacks(stacks_part), build_instructions(instructions_part)}
  end

  defp build_stacks(stacks_part) do
    stacks_part
    |> String.split("\n", trim: true)
    |> Enum.reverse()
    |> tl()
    |> Enum.map(&to_layer(&1, []))
    |> Enum.reduce(%{}, &push_layer/2)
  end

  defp to_layer("", acc), do: Enum.reverse(acc)
  defp to_layer("    " <> rest, acc), do: to_layer(rest, [?_ | acc])
  defp to_layer("   " <> rest, acc), do: to_layer(rest, [?_ | acc])
  defp to_layer(<<?[, char, ?], ?\s, rest::binary>>, acc), do: to_layer(rest, [char | acc])
  defp to_layer(<<?[, char, ?], rest::binary>>, acc), do: to_layer(rest, [char | acc])

  defp push_layer(layer, stacks) do
    layer
    |> Enum.with_index(1)
    |> Enum.reduce(stacks, fn
      {?_, _}, stacks -> stacks
      {char, i}, stacks -> Map.update(stacks, i, [char], &[char | &1])
    end)
  end

  defp build_instructions(instructions_part) do
    ~r/\d+/m
    |> Regex.scan(instructions_part)
    |> List.flatten()
    |> Enum.map(&String.to_integer/1)
    |> Enum.chunk_every(3)
    |> Enum.map(fn [amount, from, to] ->
      %{amount: amount, from: from, to: to}
    end)
  end

  defp move1(stacks, from, to, amount) do
    {from_stack, to_stack} =
      for _ <- 1..amount, reduce: {stacks[from], stacks[to]} do
        {[h | t], to_stack} -> {t, [h | to_stack]}
      end

    %{stacks | from => from_stack, to => to_stack}
  end

  defp move2(stacks, from, to, amount) do
    {to_move, from_stack} = Enum.split(stacks[from], amount)
    to_stack = to_move ++ stacks[to]

    %{stacks | from => from_stack, to => to_stack}
  end
end
input_file = "#{__DIR__}/day05.txt"
Day05.part1(input_file)
Day05.part2(input_file)