Powered by AppSignal & Oban Pro

Day 5

2022/day5.livemd

Day 5

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

Setup

input = Kino.Input.textarea("input file")

Common

defmodule Day5 do
  def prepare_input([stacks, movements]) do
    parsed_stacks = stacks |> parse_stacks()
    parsed_movements = movements |> parse_movements()

    %{stacks: parsed_stacks, movements: parsed_movements}
  end

  def process_stacks_part1(%{:stacks => stacks, :movements => movements}) do
    movements
    |> Enum.reduce(stacks, fn movement, acc1 ->
      1..movement[:qty]
      |> Enum.reduce(acc1, fn _, acc2 ->
        # pick top element
        [el | rest] = acc2[movement[:from]]

        acc2
        |> Map.put(movement[:from], rest)
        |> Map.put(movement[:to], [el | acc2[movement[:to]]])
        |> Map.new()
      end)
    end)
  end

  def process_stacks_part2(%{:stacks => stacks, :movements => movements}) do
    movements
    |> Enum.reduce(stacks, fn movement, acc1 ->
      {elements, rest} =
        acc1[movement[:from]]
        |> Enum.split(movement[:qty])

      acc1
      |> Map.put(movement[:from], rest)
      |> Map.put(movement[:to], elements ++ acc1[movement[:to]])
      |> Map.new()
    end)
  end

  def get_letters(stacks) do
    stacks
    |> Enum.map(fn {_, val} ->
      [head | _] = val
      head
    end)
    |> Enum.join()
  end

  # Private

  defp parse_movements(movements) do
    movements
    |> String.split("\n", trim: true)
    |> Enum.map(fn instruction ->
      instruction
      |> parse_instruction()
    end)
  end

  defp parse_instruction(instruction) do
    [_, qty, from, to] = Regex.run(~r/move (\d+) from (\d+) to (\d+)/, instruction)
    %{from: String.to_integer(from), to: String.to_integer(to), qty: String.to_integer(qty)}
  end

  defp parse_stacks(stacks) do
    stacks
    # I don't need the brackets
    |> String.replace(~r"\[|\]", " ")
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      row
      |> String.split("   ")
      |> Enum.map(&String.trim/1)
      |> Enum.with_index(1)
    end)
    |> remove_stack_number()
    |> Enum.group_by(&elem(&1, 1), &elem(&1, 0))
    |> Map.new(fn {k, v} -> {k, Enum.drop_while(v, &(&1 == ""))} end)
  end

  defp remove_stack_number(xs) do
    xs
    |> Enum.reverse()
    |> tl()
    |> Enum.reverse()
    |> List.flatten()
  end
end

Part 1

input
|> Kino.Input.read()
|> String.split("\n\n", trim: true)
|> Day5.prepare_input()
|> Day5.process_stacks_part1()
|> Day5.get_letters()

Part 2

input
|> Kino.Input.read()
|> String.split("\n\n", trim: true)
|> Day5.prepare_input()
|> Day5.process_stacks_part2()
|> Day5.get_letters()