Day 20. Took a while to figure it out but it was a fun one
parent
141b6af203
commit
fc2cde6509
@ -0,0 +1,112 @@
|
||||
defmodule Day20 do
|
||||
def run do
|
||||
{map, width, height, start, dest} = parse_input("data.txt")
|
||||
|
||||
# create a map of visited nodes an a queue
|
||||
visited = %{start => 100_000_000}
|
||||
q = :queue.from_list([{start, 0, []}])
|
||||
|
||||
{path, shortest} = walk(map, q, dest, visited, [], 100_000_000_000, width, height)
|
||||
path = Enum.reverse(path)
|
||||
|
||||
part1 = run(2, path)
|
||||
part2 = run(20, path)
|
||||
dbg({part1, part2})
|
||||
end
|
||||
|
||||
# walk every step along the path
|
||||
# find coordinates that we can get to within <cheat> number of steps, that save us 100 picoseconds
|
||||
defp run(cheats, path) do
|
||||
Enum.with_index(path)
|
||||
|> Enum.reduce(0, fn {el, idx}, acc ->
|
||||
res =
|
||||
path
|
||||
|> Enum.with_index()
|
||||
|> Enum.filter(fn {el2, idx2} ->
|
||||
dist = distance(el, el2)
|
||||
dist <= cheats and idx + dist + 100 <= idx2
|
||||
end)
|
||||
|> length
|
||||
|
||||
acc + res
|
||||
end)
|
||||
end
|
||||
|
||||
defp distance({x1, y1}, {x2, y2}) do
|
||||
abs(x2 - x1) + abs(y2 - y1)
|
||||
end
|
||||
|
||||
defp walk(map, q, {dx, dy} = dest, visited, paths, shortest_path, width, height) do
|
||||
# dequeue the node with the lowest distnce
|
||||
# no priority queue, so just find the lowest one and delete it from the queue
|
||||
ql = :queue.to_list(q)
|
||||
|
||||
if length(ql) == 0 do
|
||||
nil
|
||||
else
|
||||
# lowest = next_from_queue(ql, dest)
|
||||
lowest = Enum.min_by(ql, fn {_, dist, _} -> dist end)
|
||||
{{x, y}, dist, path} = lowest
|
||||
new_q = :queue.delete(lowest, q)
|
||||
|
||||
# if we reached the destination
|
||||
if x == dx and y == dy do
|
||||
{[{x, y} | path], dist}
|
||||
else
|
||||
dirs = [{1, 0}, {0, 1}, {-1, 0}, {0, -1}]
|
||||
|
||||
# find neighbors to enqueue
|
||||
neighbors =
|
||||
dirs
|
||||
|> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
|
||||
|> Enum.map(fn key -> {key, Map.get(map, key, ".")} end)
|
||||
|> Enum.filter(fn {{x, y}, el} ->
|
||||
el != "#" and x >= 0 and y >= 0 and x < width and y < height
|
||||
end)
|
||||
|> Enum.reject(fn {{nx, ny}, _el} -> Map.has_key?(visited, {nx, ny}) end)
|
||||
|> Enum.reject(fn {{nx, ny}, _el} ->
|
||||
Enum.any?(:queue.to_list(q), fn {pos, _, _} -> pos == {nx, ny} end)
|
||||
end)
|
||||
|> Enum.map(fn {key, _el} -> key end)
|
||||
|
||||
new_q2 =
|
||||
Enum.reduce(neighbors, new_q, fn {nx, ny}, acc ->
|
||||
:queue.in({{nx, ny}, dist + 1, [{x, y} | path]}, acc)
|
||||
end)
|
||||
|
||||
new_visited = Map.put(visited, {x, y}, dist)
|
||||
walk(map, new_q2, dest, new_visited, paths, shortest_path, width, height)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_input(file) do
|
||||
map_list =
|
||||
File.read!(file)
|
||||
|> String.trim()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&String.graphemes/1)
|
||||
|
||||
height = length(map_list)
|
||||
width = length(hd(map_list))
|
||||
|
||||
# convert the map List to a map Map, grabbing start and dest along the way
|
||||
{map, start, dest} =
|
||||
Enum.concat(map_list)
|
||||
|> Enum.with_index()
|
||||
|> Enum.reduce({%{}, nil, nil}, fn {el, idx}, {map, start, dest} ->
|
||||
row = div(idx, height)
|
||||
col = rem(idx, width)
|
||||
|
||||
map = Map.put(map, {row, col}, el)
|
||||
start = if el == "S", do: {row, col}, else: start
|
||||
dest = if el == "E", do: {row, col}, else: dest
|
||||
|
||||
{map, start, dest}
|
||||
end)
|
||||
|
||||
{map, width, height, start, dest}
|
||||
end
|
||||
end
|
||||
|
||||
Day20.run()
|
Loading…
Reference in New Issue