Mix.install([:libgraph])
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
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