Powered by AppSignal & Oban Pro

Day 12

2022/day12.livemd

Day 12

Mix.install([:kino, :qex])

Input

input = Kino.Input.textarea("")

Pre-work

defmodule Climb do
  def min_steps(grid, part2 \\ false) do
    min_steps(
      grid,
      heights(grid),
      initial_queue(grid, part2),
      map_size(grid),
      map_size(grid[0]),
      MapSet.new()
    )
  end

  def min_steps(grid, heights, q, h, w, visited) do
    {{i, j, steps}, q} = Qex.pop!(q)

    cond do
      {i, j} in visited ->
        min_steps(grid, heights, q, h, w, visited)

      grid[i][j] == ?E ->
        steps

      true ->
        q =
          Enum.reduce([{-1, 0}, {0, -1}, {1, 0}, {0, 1}], q, fn {di, dj}, q ->
            y = i + di
            x = j + dj

            if y >= 0 and y < h and x >= 0 and x < w and heights[y][x] <= 1 + heights[i][j] do
              Qex.push(q, {y, x, steps + 1})
            else
              q
            end
          end)

        min_steps(grid, heights, q, h, w, MapSet.put(visited, {i, j}))
    end
  end

  defp heights(grid) do
    grid
    |> Enum.map(fn {i, row} ->
      row
      |> Enum.map(fn
        {j, ?S} -> {j, ?a}
        {j, ?E} -> {j, ?z}
        tup -> tup
      end)
      |> then(&amp;{i, Map.new(&amp;1)})
    end)
    |> Map.new()
  end

  defp initial_queue(grid, part2) do
    Enum.reduce(grid, Qex.new(), fn {i, row}, acc ->
      Enum.reduce(row, acc, fn
        {j, ?S}, acc -> Qex.push(acc, {i, j, 0})
        {j, ?a}, acc when part2 -> Qex.push(acc, {i, j, 0})
        _, acc -> acc
      end)
    end)
  end
end

grid =
  input
  |> Kino.Input.read()
  |> String.split()
  |> Enum.with_index()
  |> Enum.map(fn {line, i} ->
    line
    |> String.to_charlist()
    |> Enum.with_index()
    |> Enum.map(fn {c, j} -> {j, c} end)
    |> Map.new()
    |> then(&amp;{i, &amp;1})
  end)
  |> Map.new()

Part 1

Climb.min_steps(grid)

Part 2

Climb.min_steps(grid, true)