Powered by AppSignal & Oban Pro

Day 12: Hill Climbing Algorithm

2022/day12.livemd

Day 12: Hill Climbing Algorithm

Mix.install([:kino])

Section

input = Kino.Input.textarea("input")
grid =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.map(fn line ->
    line
    |> String.to_charlist()
    |> Enum.map(fn
      ?S -> 0
      ?E -> 27
      c -> c - ?a + 1
    end)
  end)
  |> Enum.with_index(0)
  |> Enum.map(fn {row, i} ->
    row
    |> Enum.with_index(0)
    |> Enum.map(fn {col, j} ->
      {{i, j}, col}
    end)
  end)
  |> List.flatten()
  |> Map.new()

start =
  grid
  |> Enum.find_value(fn
    {p, 0} -> p
    _ -> false
  end)

top =
  grid
  |> Enum.find_value(fn
    {p, 27} -> p
    _ -> false
  end)

defmodule BFS do
  def search(grid, path, seen, from \\ -1) do
    {x, y} = current = hd(path)
    h = grid[current]

    seen = Map.put(seen, current, path)

    grid
    |> Map.take([{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}])
    |> Enum.reject(fn {pos, height} ->
      # too high, or known shorter path
      height > h + 1 or (seen[pos] && length(seen[pos]) <= length(path) + 1)
    end)
    |> case do
      [] ->
        # no new nodes the explore, return known shortest paths 
        seen

      list ->
        Enum.reduce(list, seen, fn
          {pos, ^from}, seen ->
            search(grid, [pos], seen, from)

          {pos, _}, seen ->
            search(grid, [pos | path], seen, from)
        end)
    end
  end
end

Part 1

BFS.search(grid, [start], %{})[top] |> Enum.count() |> Kernel.-(1)

Part 2

BFS.search(grid, [start], %{}, 1)[top] |> Enum.count() |> Kernel.-(1)