Advent of Code 2022 - Day 12
Challenge
— 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?
Your puzzle answer was 520.
— 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?
Your puzzle answer was 508.
Solution
This solution is written in Lua
.
function printMatrix(matrix)
for i = 1, #matrix do
for j = 1, #matrix[i] do
io.write(matrix[i][j])
end
print() -- new line
end
end
function getNext(matrix, row, column, length)
if column < length then
return matrix[row][column + 1]
else
return nil
end
end
function getLast(matrix, row, column)
if column > 1 then
return matrix[row][column - 1]
else
return nil
end
end
function getUpper(matrix, row, column)
if row > 1 then
return matrix[row - 1][column]
else
return nil
end
end
function getLower(matrix, row, column, height)
if row < height then
return matrix[row + 1][column]
else
return nil
end
end
function getElevation(e)
if e == "E" then
return string.byte("z")
end
return string.byte(e)
end
local file = io.open("../input.txt", "r") -- read file
local matrix = {}
local row = 0
local length = 0
local height = 0
local posE = {}
local posS = {}
local aIndex = {}
local aCount = 0
for line in file:lines() do
-- line by line
= row + 1
row = #line
length = row
height [row] = {}
matrixfor column = 1, #line do
-- length of line
local ch = line:sub(column, column) -- char at index
[row][column] = ch
matrixif ch == "E" then
["row"] = row
posE["col"] = column
posEend
if ch == "S" then
["row"] = row
posS["col"] = column
posSend
if ch == "a" then
= aCount + 1
aCount [aCount] = row * length + column - length
aIndexend
end
end
local edges = {}
-- form graph
for i = 1, #matrix do
for j = 1, #matrix[i] do
= i * length + j - length
index [index] = {}
edgeslocal current = matrix[i][j]
local last, next, up, down = getLast(matrix, i, j), getNext(matrix, i, j, length), getUpper(matrix, i, j), getLower(matrix, i, j, height)
if last ~= nil then
if getElevation(last) <= getElevation(current) + 1 or current == "S" then
[index][index - 1] = 1
edges--print(index, index - 1)
end
end
if next ~= nil then
if getElevation(next) <= getElevation(current) + 1 or current == "S" then
[index][index + 1] = 1
edges--print(index, index + 1)
end
end
if up ~= nil then
if getElevation(up) <= getElevation(current) + 1 or current == "S" then
[index][index - length] = 1
edges--print(index, index - length)
end
end
if down ~= nil then
if getElevation(down) <= getElevation(current) + 1 or current == "S" then
[index][index + length] = 1
edges--print(index, index + length)
end
end
end
end
local starting_vertex = posS["row"] * length + posS["col"] - length
local destination_vertex = posE["row"] * length + posE["col"] -length
-- below code is from stackoverflow: https://stackoverflow.com/a/55919336
function p1(_starting_vertex)
local function create_dijkstra(_starting_vertex)
local shortest_paths = { [_starting_vertex] = { full_distance = 0 } }
local vertex, distance, heap_size, heap = _starting_vertex, 0, 0, {}
return
function(adjacent_vertex, edge_length)
if adjacent_vertex then
-- receiving the information about adjacent vertex
local new_distance = distance + edge_length
local adjacent_vertex_info = shortest_paths[adjacent_vertex]
local pos
if adjacent_vertex_info then
if new_distance < adjacent_vertex_info.full_distance then
.full_distance = new_distance
adjacent_vertex_info.previous_vertex = vertex
adjacent_vertex_info= adjacent_vertex_info.index
pos else
return
end
else
= { full_distance = new_distance, previous_vertex = vertex, index = 0 }
adjacent_vertex_info [adjacent_vertex] = adjacent_vertex_info
shortest_paths= heap_size + 1
heap_size = heap_size
pos end
while pos > 1 do
local parent_pos = (pos - pos % 2) / 2
local parent = heap[parent_pos]
local parent_info = shortest_paths[parent]
if new_distance < parent_info.full_distance then
[pos] = parent
heap.index = pos
parent_info= parent_pos
pos else
break
end
end
[pos] = adjacent_vertex
heap.index = pos
adjacent_vertex_infoelseif heap_size > 0 then
-- which vertex neighborhood to ask for?
= heap[1]
vertex local parent = heap[heap_size]
[heap_size] = nil
heap= heap_size - 1
heap_size if heap_size > 0 then
local pos = 1
local last_node_pos = heap_size / 2
local parent_info = shortest_paths[parent]
local parent_distance = parent_info.full_distance
while pos <= last_node_pos do
local child_pos = pos + pos
local child = heap[child_pos]
local child_info = shortest_paths[child]
local child_distance = child_info.full_distance
if child_pos < heap_size then
local child_pos2 = child_pos + 1
local child2 = heap[child_pos2]
local child2_info = shortest_paths[child2]
local child2_distance = child2_info.full_distance
if child2_distance < child_distance then
= child_pos2
child_pos = child2
child = child2_info
child_info = child2_distance
child_distance end
end
if child_distance < parent_distance then
[pos] = child
heap.index = pos
child_info= child_pos
pos else
break
end
end
[pos] = parent
heap.index = pos
parent_infoend
local vertex_info = shortest_paths[vertex]
.index = nil
vertex_info= vertex_info.full_distance
distance return vertex
end
end,
shortest_pathsend
local vertex, dijkstra, shortest_paths = _starting_vertex, create_dijkstra(_starting_vertex)
while vertex and vertex ~= destination_vertex do
-- send information about all adjacent vertexes of "vertex"
for adjacent_vertex, edge_length in pairs(edges[vertex]) do
(adjacent_vertex, edge_length)
dijkstraend
= dijkstra() -- now dijkstra is asking you about the neighborhood of another vertex
vertex end
if vertex then
local full_distance = shortest_paths[vertex].full_distance
local path = vertex
while vertex do
= shortest_paths[vertex].previous_vertex
vertex if vertex then
= vertex .. " " .. path
path end
end
--print(full_distance, path)
return full_distance
else
--print "Path not found"
end
end
print("part I:", p1(starting_vertex))
local minLength = 2147483647
for i = 1, #aIndex do
local len = p1(aIndex[i])
if len ~= nil then -- nil if path not found
--print(aIndex[i], len)
if len < minLength then
= len
minLength end
end
end
print("part II:", minLength)
--printMatrix(matrix)