Powered by AppSignal & Oban Pro

Day 5

2022/day_05.livemd

Day 5

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

Common

defmodule Common do
  def parse_input(textarea) do
    [stacks, moves] =
      textarea
      |> Kino.Input.read()
      |> String.split("\n\n", trim: true)

    {
      parse_stacks(stacks),
      parse_moves(moves)
    }
  end

  defp parse_stacks(stacks) do
    [positions | stacks] =
      stacks
      |> String.split("\n", trim: true)
      |> Enum.reverse()

    stacks_count =
      positions
      |> String.split()
      |> List.last()
      |> String.to_integer()

    Enum.reduce(stacks, %{}, fn line, stacks ->
      Enum.reduce(1..stacks_count, stacks, fn stack_index, stacks ->
        grapheme_position = fn
          1 -> 1
          pos -> (pos - 1) * 4 + 1
        end

        grapheme_position = grapheme_position.(stack_index)
        crate = String.at(line, grapheme_position)

        case crate do
          " " -> stacks
          nil -> stacks
          crate -> Map.update(stacks, stack_index, [crate], &[crate | &1])
        end
      end)
    end)
  end

  defp parse_moves(moves) do
    moves
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      ["move", count, "from", from, "to", to] = String.split(line)

      %{
        count: String.to_integer(count),
        from: String.to_integer(from),
        to: String.to_integer(to)
      }
    end)
  end
end

Input

textarea =
  Kino.Input.textarea("Input:",
    default: """
        [D]
    [N] [C]
    [Z] [M] [P]
     1   2   3 

    move 1 from 2 to 1
    move 3 from 1 to 3
    move 2 from 2 to 1
    move 1 from 1 to 2
    """
  )
input = Common.parse_input(textarea)

Part 1

defmodule Part1 do
  def run({stacks, moves}) do
    moves
    |> Enum.reduce(stacks, &move(&2, &1))
    |> Enum.sort()
    |> Enum.map(fn {_, [crate | _rest]} -> crate end)
    |> Enum.join()
  end

  defp move(stacks, %{count: count, from: from, to: to}) do
    Enum.reduce(1..count, stacks, fn _, stacks ->
      crate =
        stacks
        |> Map.fetch!(from)
        |> hd()

      stacks
      |> Map.update!(from, &tl/1)
      |> Map.update!(to, fn stack -> [crate | stack] end)
    end)
  end
end
Part1.run(input)

Part 2

defmodule Part2 do
  def run({stacks, moves}) do
    moves
    |> Enum.reduce(stacks, &move(&2, &1))
    |> Enum.sort()
    |> Enum.map(fn {_, [crate | _rest]} -> crate end)
    |> Enum.join()
  end

  defp move(stacks, %{count: count, from: from, to: to} = move) do
    crates =
      stacks
      |> Map.fetch!(from)
      |> Enum.take(count)

    stacks
    |> Map.update!(from, &Enum.drop(&1, count))
    |> Map.update!(to, fn stack -> crates ++ stack end)
  end
end
Part2.run(input)