Skip to content

Instantly share code, notes, and snippets.

@iautom8things
Created December 4, 2024 14:00
Show Gist options
  • Save iautom8things/a0d0c66daa07d89ed36856c97937b824 to your computer and use it in GitHub Desktop.
Save iautom8things/a0d0c66daa07d89ed36856c97937b824 to your computer and use it in GitHub Desktop.

Day 12

Mix.install([:libgraph])

Section

--- Day 12: Hill Climbing Algorithm ---

You try contacting the Elves using your handheld device, but the river you're following must be too low to get a decent signal.

You ask the device for a heightmap of the surrounding area (your puzzle input). The heightmap shows the local area from above broken into a grid; the elevation of each square of the grid is given by a single lowercase letter, where a is the lowest elevation, b is the next-lowest, and so on up to the highest elevation, z.

Also included on the heightmap are marks for your current position (S) and the location that should get the best signal (E). Your current position (S) has elevation a, and the location that should get the best signal (E) has elevation z.

You'd like to reach E, but to save energy, you should do it in as few steps as possible. During each step, you can move exactly one square up, down, left, or right. To avoid needing to get out your climbing gear, the elevation of the destination square can be at most one higher than the elevation of your current square; that is, if your current elevation is m, you could step to elevation n, but not to elevation o. (This also means that the elevation of the destination square can be much lower than the elevation of your current square.)

For example:

Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi

Here, you start in the top-left corner; your goal is near the middle. You could start by moving down or right, but eventually you'll need to head toward the e at the bottom. From there, you can spiral around to the goal:

v..v<<<<
>v.vv<<^
.>vv>E^^
..v>>>^^
..>>>>>^

In the above diagram, the symbols indicate whether the path exits each square moving up (^), down (v), left (<), or right (>). The location that should get the best signal is still E, and . marks unvisited squares.

This path reaches the goal in 31 steps, the fewest possible.

What is the fewest steps required to move from your current position to the location that should get the best signal?

test_input_part1 =
  """
  Sabqponm
  abcryxxl
  accszExk
  acctuvwj
  abdefghi
  """
  |> String.trim()
"Sabqponm\nabcryxxl\naccszExk\nacctuvwj\nabdefghi"
input_part1 =
  """
  abcccccccaaaaaccccaaaaaaaccccccccccccccccccccccccccccccccccccaaaaa
  abaacccaaaaaaccccccaaaaaaaaaaaaaccccccccccccccccccccccccccccaaaaaa
  abaacccaaaaaaaccccaaaaaaaaaaaaaacccccccccccccaacccccccccccccaaaaaa
  abaacccccaaaaaacaaaaaaaaaaaaaaaacccccccccccccaacccccccccccccacacaa
  abaccccccaaccaacaaaaaaaaaacccaacccccccccccccaaacccccccccccccccccaa
  abcccccccaaaacccaaaaaaaaacccccccccccccaaacccaaacccccccccccccccccaa
  abccccccccaaaccccccccaaaacccccccccccccaaaaacaaaccacacccccccccccccc
  abccccccccaaacaaacccccaaacccccccccccccaaaaaaajjjjjkkkcccccaacccccc
  abcccccaaaaaaaaaacccccaaccccccccccciiiiiijjjjjjjjjkkkcaaaaaacccccc
  abcccccaaaaaaaaacccccccccccccccccciiiiiiijjjjjjjrrkkkkaaaaaaaacccc
  abcccccccaaaaaccccccccccccccccccciiiiiiiijjjjrrrrrppkkkaaaaaaacccc
  abcccaaccaaaaaacccccccccccaacaaciiiiqqqqqrrrrrrrrpppkkkaaaaaaacccc
  abccaaaaaaaaaaaaccccacccccaaaaaciiiqqqqqqrrrrrruuppppkkaaaaacccccc
  abcccaaaaaaacaaaacaaacccccaaaaaahiiqqqqtttrrruuuuupppkkaaaaacccccc
  abcaaaaaaaccccaaaaaaacccccaaaaaahhqqqtttttuuuuuuuuuppkkkccaacccccc
  abcaaaaaaaaccccaaaaaacccccaaaaaahhqqqtttttuuuuxxuuuppkklcccccccccc
  abcaaaaaaaacaaaaaaaaaaacccccaaachhhqqtttxxxuuxxyyuuppllllccccccccc
  abcccaaacaccaaaaaaaaaaaccccccccchhhqqtttxxxxxxxyuupppplllccccccccc
  abaacaacccccaaaaaaaaaaaccccccccchhhqqtttxxxxxxyyvvvpppplllcccccccc
  abaacccccccccaaaaaaacccccccccccchhhpppttxxxxxyyyvvvvpqqqlllccccccc
  SbaaccccccaaaaaaaaaaccccccccccchhhppptttxxxEzzyyyyvvvqqqlllccccccc
  abaaaaccccaaaaaaaaacccccccccccchhhpppsssxxxyyyyyyyyvvvqqqlllcccccc
  abaaaacccccaaaaaaaacccccccccccgggpppsssxxyyyyyyyyyvvvvqqqlllcccccc
  abaaacccaaaacaaaaaaaccccccccccgggpppsswwwwwwyyyvvvvvvqqqllllcccccc
  abaaccccaaaacaaccaaaacccccccccgggppssswwwwwwyyywvvvvqqqqmmmccccccc
  abaaccccaaaacaaccaaaaccaaaccccggpppssssswwswwyywvqqqqqqmmmmccccccc
  abcccccccaaacccccaaacccaaacaccgggpppssssssswwwwwwrqqmmmmmccccccccc
  abcccccccccccccccccccaacaaaaacgggppooosssssrwwwwrrrmmmmmcccccccccc
  abcccccccccccccccccccaaaaaaaacggggoooooooorrrwwwrrnmmmdddccaaccccc
  abaccccccccccccaacccccaaaaaccccggggoooooooorrrrrrrnmmddddcaaaccccc
  abaccccccccaaaaaaccccccaaaaaccccggfffffooooorrrrrnnndddddaaaaccccc
  abaacccccccaaaaaacccccaaaaaacccccffffffffoonrrrrrnnndddaaaaaaacccc
  abaaccccccccaaaaaaaccacaaaacccccccccffffffonnnnnnnndddaaaaaaaacccc
  abccccccccccaaaaaaaaaaaaaaaccccccccccccfffennnnnnnddddccaaaccccccc
  abcccccccccaaaaaaacaaaaaaaaaacccccccccccffeennnnnedddccccaaccccccc
  abcccccccccaaaaaaccaaaaaaaaaaaccccccccccaeeeeeeeeeedcccccccccccccc
  abccccccccccccaaaccaaaaaaaaaaaccccccccccaaaeeeeeeeecccccccccccccaa
  abcccccccaaccccccccaaaaaaaacccccccccccccaaaceeeeecccccccccccccccaa
  abaaccaaaaaaccccccccaaaaaaaacccccccccccccaccccaaacccccccccccaaacaa
  abaaccaaaaacccccaaaaaaaaaaacccccccccccccccccccccacccccccccccaaaaaa
  abaccaaaaaaaaccaaaaaaaaaaaaaacccccccccccccccccccccccccccccccaaaaaa
  """
  |> String.trim()
"abcccccccaaaaaccccaaaaaaaccccccccccccccccccccccccccccccccccccaaaaa\nabaacccaaaaaaccccccaaaaaaaaaaaaaccccccccccccccccccccccccccccaaaaaa\nabaacccaaaaaaaccccaaaaaaaaaaaaaacccccccccccccaacccccccccccccaaaaaa\nabaacccccaaaaaacaaaaaaaaaaaaaaaacccccccccccccaacccccccccccccacacaa\nabaccccccaaccaacaaaaaaaaaacccaacccccccccccccaaacccccccccccccccccaa\nabcccccccaaaacccaaaaaaaaacccccccccccccaaacccaaacccccccccccccccccaa\nabccccccccaaaccccccccaaaacccccccccccccaaaaacaaaccacacccccccccccccc\nabccccccccaaacaaacccccaaacccccccccccccaaaaaaajjjjjkkkcccccaacccccc\nabcccccaaaaaaaaaacccccaaccccccccccciiiiiijjjjjjjjjkkkcaaaaaacccccc\nabcccccaaaaaaaaacccccccccccccccccciiiiiiijjjjjjjrrkkkkaaaaaaaacccc\nabcccccccaaaaaccccccccccccccccccciiiiiiiijjjjrrrrrppkkkaaaaaaacccc\nabcccaaccaaaaaacccccccccccaacaaciiiiqqqqqrrrrrrrrpppkkkaaaaaaacccc\nabccaaaaaaaaaaaaccccacccccaaaaaciiiqqqqqqrrrrrruuppppkkaaaaacccccc\nabcccaaaaaaacaaaacaaacccccaaaaaahiiqqqqtttrrruuuuupppkkaaaaacccccc\nabcaaaaaaaccccaaaaaaacccccaaaaaahhqqqtttttuuuuuuuuuppkkkccaacccccc\nabcaaaaaaaaccccaaaaaacccccaaaaaahhqqqtttttuuuuxxuuuppkklcccccccccc\nabcaaaaaaaacaaaaaaaaaaacccccaaachhhqqtttxxxuuxxyyuuppllllccccccccc\nabcccaaacaccaaaaaaaaaaaccccccccchhhqqtttxxxxxxxyuupppplllccccccccc\nabaacaacccccaaaaaaaaaaaccccccccchhhqqtttxxxxxxyyvvvpppplllcccccccc\nabaacccccccccaaaaaaacccccccccccchhhpppttxxxxxyyyvvvvpqqqlllccccccc\nSbaaccccccaaaaaaaaaaccccccccccchhhppptttxxxEzzyyyyvvvqqqlllccccccc\nabaaaaccccaaaaaaaaacccccccccccchhhpppsssxxxyyyyyyyyvvvqqqlllcccccc\nabaaaacccccaaaaaaaacccccccccccgggpppsssxxyyyyyyyyyvvvvqqqlllcccccc\nabaaacccaaaacaaaaaaaccccccccccgggpppsswwwwwwyyyvvvvvvqqqllllcccccc\nabaaccccaaaacaaccaaaacccccccccgggppssswwwwwwyyywvvvvqqqqmmmccccccc\nabaaccccaaaacaaccaaaaccaaaccccggpppssssswwswwyywvqqqqqqmmmmccccccc\nabcccccccaaacccccaaacccaaacaccgggpppssssssswwwwwwrqqmmmmmccccccccc\nabcccccccccccccccccccaacaaaaacgggppooosssssrwwwwrrrmmmmmcccccccccc\nabcccccccccccccccccccaaaaaaaacggggoooooooorrrwwwrrnmmmdddccaaccccc\nabaccccccccccccaacccccaaaaaccccggggoooooooorrrrrrrnmmddddcaaaccccc\nabaccccccccaaaaaaccccccaaaaaccccggfffffooooorrrrrnnndddddaaaaccccc\nabaacccccccaaaaaacccccaaaaaacccccffffffffoonrrrrrnnndddaaaaaaacccc\nabaaccccccccaaaaaaaccacaaaacccccccccffffffonnnnnnnndddaaaaaaaacccc\nabccccccccccaaaaaaaaaaaaaaaccccccccccccfffennnnnnnddddccaaaccccccc\nabcccccccccaaaaaaacaaaaaaaaaacccccccccccffeennnnnedddccccaaccccccc\nabcccccccccaaaaaaccaaaaaaaaaaaccccccccccaeeeeeeeeeedcccccccccccccc\nabccccccccccccaaaccaaaaaaaaaaaccccccccccaaaeeeeeeeecccccccccccccaa\nabcccccccaaccccccccaaaaaaaacccccccccccccaaaceeeeecccccccccccccccaa\nabaaccaaaaaaccccccccaaaaaaaacccccccccccccaccccaaacccccccccccaaacaa\nabaaccaaaaacccccaaaaaaaaaaacccccccccccccccccccccacccccccccccaaaaaa\nabaccaaaaaaaaccaaaaaaaaaaaaaacccccccccccccccccccccccccccccccaaaaaa"
alias Graph.Edge

defmodule Solution do
  def run_part1(input) do
    input
    |> String.split("\n")
    |> Enum.map(&String.to_charlist/1)
    |> to_graph()
  end

  def to_graph([row | _] = map) do
    rows = Enum.count(map)
    cols = Enum.count(row)

    all_as =
      for r <- 0..(rows - 1),
          c <- 0..(cols - 1) do
        case at_map_square(map, {r, c}) do
          {:a, _, _} -> {r, c}
          _ -> nil
        end
      end
      |> Enum.reject(&is_nil(&1))

    edges =
      for r <- 0..(rows - 1),
          c <- 0..(cols - 1),
          other_r <- [r - 1, r, r + 1],
          other_c <- [c - 1, c, c + 1] do
        label =
          case {r - other_r, c - other_c} do
            {1, 0} -> :up
            {-1, 0} -> :down
            {0, -1} -> :left
            {0, 1} -> :right
            _ -> :ignore
          end

        [{at_map_square(map, {r, c}), at_map_square(map, {other_r, other_c}), label}]
      end
      |> List.flatten()
      |> Enum.reject(fn
        {_, _, :ignore} -> true
        {{:error, _}, _, _} -> true
        {_, {:error, _}, _} -> true
        _ -> false
      end)

    start = Enum.find(edges, fn {{label, _, _}, _, _} -> label == :S end)
    destination = Enum.find(edges, fn {{label, _, _}, _, _} -> label == :E end)

    edges =
      edges
      |> Enum.map(&to_edge/1)
      |> Enum.reject(&(&1 == {:error, :too_high}))

    %{
      all_as: all_as,
      start: start,
      destination: destination,
      graph: Graph.new() |> Graph.add_edges(edges)
    }
  end

  def to_edge({{_v1, v1_coords, v1_val}, {_v2, v2_coords, v2_val}, direction}) do
    if v2_val > v1_val + 1 do
      {:error, :too_high}
    else
      Edge.new(v1_coords, v2_coords, label: direction)
    end
  end

  def at_map_square([row | _] = map, {r, c})
      when r >= 0 and r < length(map) and c >= 0 and c < length(row) do
    element = map |> Enum.at(r) |> Enum.at(c)
    label = [element] |> to_string() |> String.to_atom()
    {label, {r, c}, value_of(element)}
  end

  def at_map_square(_, _), do: {:error, :out_of_bounds}

  def value_of(?S), do: 1
  def value_of(?E), do: 26

  def value_of(label) when label in ?a..?z do
    label - ?a + 1
  end
end
{:module, Solution, <<70, 79, 82, 49, 0, 0, 27, ...>>, {:value_of, 1}}
test_result_part1 = test_input_part1 |> Solution.run_part1()
%{
  all_as: [{0, 1}, {1, 0}, {2, 0}, {3, 0}, {4, 0}],
  destination: {{:E, {2, 5}, 26}, {:x, {1, 5}, 24}, :up},
  graph: #Graph<type: directed, vertices: [
    {4, 0},
    {2, 1},
    {2, 0},
    {3, 5},
    {0, 4},
    {3, 6},
    {3, 1},
    {4, 5},
    {0, 3},
    {1, 7},
    {3, 7},
    {0, 1},
    {2, 7},
    {1, 1},
    {2, 5},
    {4, 6},
    {2, 4},
    {1, 4},
    {4, 1},
    {2, 6},
    {0, 7},
    {3, 4},
    {3, 2},
    {2, 3},
    {1, 5},
    {0, 6},
    {0, 5},
    {0, 0},
    {2, 2},
    {4, 7},
    {3, 3},
    {1, 0},
    {4, 2},
    {1, 2},
    {0, 2},
    {3, 0},
    {4, 3},
    {1, 6},
    {1, 3},
    {4, 4}
  ], edges: [{4, 0} -[up]-> {3, 0}, {4, 0} -[left]-> {4, 1}, {2, 1} -[left]-> {2, 2}, {2, 1} -[right]-> {2,
   0}, {2, 1} -[up]-> {1, 1}, {2, 1} -[down]-> {3, 1}, {2, 0} -[up]-> {1, 0}, {2, 0} -[down]-> {3,
   0}, {3, 5} -[right]-> {3, 4}, {3, 5} -[down]-> {4, 5}, {3, 5} -[left]-> {3, 6}, {0, 4} -[right]-> {0,
   3}, {0, 4} -[left]-> {0, 5}, {3, 6} -[left]-> {3, 7}, {3, 6} -[down]-> {4, 6}, {3, 6} -[right]-> {3,
   5}, {3, 6} -[up]-> {2, 6}, {3, 1} -[up]-> {2, 1}, {3, 1} -[right]-> {3, 0}, {3, 1} -[left]-> {3,
   2}, {3, 1} -[down]-> {4, 1}, {4, 5} -[left]-> {4, 6}, {4, 5} -[right]-> {4, 4}, {0, 3} -[right]-> {0,
   2}, {0, 3} -[left]-> {0, 4}, {0, 3} -[down]-> {1, 3}, {1, 7} -[down]-> {2, 7}, {1, 7} -[up]-> {0,
   7}, {3, 7} -[up]-> {2, 7}, {3, 7} -[down]-> {4, 7}, {0, 1} -[right]-> {0, 0}, {0, 1} -[down]-> {1,
   1}, {0, 1} -[left]-> {0, 2}, {2, 7} -[down]-> {3, 7}, {2, 7} -[up]-> {1, 7}, {1, 1} -[up]-> {0,
   1}, {1, 1} -[down]-> {2, 1}, {1, 1} -[right]-> {1, 0}, {1, 1} -[left]-> {1, 2}, {2, 5} -[up]-> {1,
   5}, {2, 5} -[down]-> {3, 5}, {2, 5} -[right]-> {2, 4}, {2, 5} -[left]-> {2, 6}, {4, 6} -[left]-> {4,
   7}, {4, 6} -[right]-> {4, 5}, {2, 4} -[down]-> {3, 4}, {2, 4} -[up]-> {1, 4}, {2, 4} -[right]-> {2,
   3}, {2, 4} -[left]-> {2, 5}, {1, 4} -[left]-> {1, 5}, {1, 4} -[down]-> {2, 4}, {1, 4} -[up]-> {0,
   4}, {1, 4} -[right]-> {1, 3}, {4, 1} -[right]-> {4, 0}, {4, 1} -[up]-> {3, 1}, {2, 6} -[up]-> {1,
   6}, {2, 6} -[left]-> {2, 7}, {2, 6} -[down]-> {3, 6}, {0, 7} -[right]-> {0, 6}, {0, 7} -[down]-> {1,
   7}, {3, 4} -[right]-> {3, 3}, {3, 4} -[left]-> {3, 5}, {3, 4} -[down]-> {4, 4}, {3, 2} -[up]-> {2,
   2}, {3, 2} -[right]-> {3, 1}, {3, 2} -[down]-> {4, 2}, {2, 3} -[down]-> {3, 3}, {2, 3} -[right]-> {2,
   2}, {2, 3} -[up]-> {1, 3}, {1, 5} -[left]-> {1, 6}, {1, 5} -[right]-> {1, 4}, {1, 5} -[up]-> {0,
   5}, {0, 6} -[right]-> {0, 5}, {0, 6} -[left]-> {0, 7}, {0, 5} -[left]-> {0, 6}, {0, 5} -[right]-> {0,
   4}, {0, 0} -[left]-> {0, 1}, {0, 0} -[down]-> {1, 0}, {2, 2} -[right]-> {2, 1}, {2, 2} -[down]-> {3,
   2}, {2, 2} -[up]-> {1, 2}, {4, 7} -[up]-> {3, 7}, {4, 7} -[right]-> {4, 6}, {3, 3} -[left]-> {3,
   4}, {3, 3} -[down]-> {4, 3}, {3, 3} -[up]-> {2, 3}, {3, 3} -[right]-> {3, 2}, {1, 0} -[down]-> {2,
   0}, {1, 0} -[up]-> {0, 0}, {1, 0} -[left]-> {1, 1}, {4, 2} -[left]-> {4, 3}, {4, 2} -[up]-> {3,
   2}, {4, 2} -[right]-> {4, 1}, {1, 2} -[down]-> {2, 2}, {1, 2} -[right]-> {1, 1}, {1, 2} -[up]-> {0,
   2}, {0, 2} -[right]-> {0, 1}, {0, 2} -[down]-> {1, 2}, {3, 0} -[up]-> {2, 0}, {3, 0} -[down]-> {4,
   0}, {4, 3} -[left]-> {4, 4}, {4, 3} -[right]-> {4, 2}, {1, 6} -[right]-> {1, 5}, {1, 6} -[up]-> {0,
   6}, {1, 6} -[down]-> {2, 6}, {1, 6} -[left]-> {1, 7}, {1, 3} -[up]-> {0, 3}, {1, 3} -[down]-> {2,
   3}, {1, 3} -[right]-> {1, 2}, {4, 4} -[right]-> {4, 3}, {4, 4} -[left]-> {4, 5}]>,
  start: {{:S, {0, 0}, 1}, {:a, {0, 1}, 1}, :left}
}
test_result_part1.graph |> Graph.get_shortest_path({0, 0}, {2, 5}) |> Enum.count()
32
[{0, 0} | test_result_part1.all_as]
|> Enum.map(&Graph.get_shortest_path(test_result_part1.graph, &1, {2, 5}))
|> Enum.map(&Enum.count/1)
|> Enum.min()
30
result_part1 = input_part1 |> Solution.run_part1()
%{
  all_as: [
    {0, 0},
    {0, 9},
    {0, 10},
    {0, 11},
    {0, 12},
    {0, 13},
    {0, 18},
    {0, 19},
    {0, 20},
    {0, 21},
    {0, 22},
    {0, 23},
    {0, 24},
    {0, 61},
    {0, 62},
    {0, 63},
    {0, 64},
    {0, 65},
    {1, 0},
    {1, 2},
    {1, 3},
    {1, 7},
    {1, 8},
    {1, 9},
    {1, 10},
    {1, 11},
    {1, 12},
    {1, 19},
    {1, 20},
    {1, 21},
    {1, 22},
    {1, 23},
    {1, 24},
    {1, 25},
    {1, 26},
    {1, 27},
    {1, 28},
    {1, 29},
    {1, 30},
    {1, 31},
    {1, 60},
    {1, 61},
    {1, 62},
    {1, 63},
    {1, 64},
    {1, 65},
    {2, 0},
    {2, ...},
    {...},
    ...
  ],
  destination: {{:E, {20, 43}, 26}, {:x, {19, 43}, 24}, :up},
  graph: #Graph<type: directed, num_vertices: 2706, num_edges: 9689>,
  start: {{:S, {20, 0}, 1}, {:a, {19, 0}, 1}, :up}
}
result_part1.graph
|> Graph.get_shortest_path({20, 0}, {20, 43})
|> Enum.count()
|> Kernel.-(1)
339

--- Part Two ---

As you walk up the hill, you suspect that the Elves will want to turn this into a hiking trail. The beginning isn't very scenic, though; perhaps you can find a better starting point.

To maximize exercise while hiking, the trail should start as low as possible: elevation a. The goal is still the square marked E. However, the trail should still be direct, taking the fewest steps to reach its goal. So, you'll need to find the shortest path from any square at elevation a to the square marked E.

Again consider the example from above:

Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi

Now, there are six choices for starting position (five marked a, plus the square marked S that counts as being at elevation a). If you start at the bottom-left square, you can reach the goal most quickly:

...v<<<<
...vv<<^
...v>E^^
.>v>>>^^
>^>>>>>^

This path reaches the goal in only 29 steps, the fewest possible.

What is the fewest steps required to move starting from any square with elevation a to the location that should get the best signal?

[{20, 0} | result_part1.all_as]
|> Enum.map(&Graph.get_shortest_path(result_part1.graph, &1, {20, 43}))
|> Enum.reject(&is_nil/1)
|> Enum.map(&Enum.count/1)
|> Enum.min()
|> Kernel.-(1)
332
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment