kwando
Advent Of Code 2022 - Day 12
Phew, this one took a while to get right. My naive attempts was way to slow so I reached for Dijkstras shortest path algorithm… and that payed off pretty well for part 2 ![]()
~1s on my machine, I have not bothered to cleanup and optimize it yet… you have been warned ![]()
Most Liked
code-shoily
Took it as an opportunity to learn me some Vegalite too.
Thank you @kwando for inspiring me to do so.
Basic stuff, will dive deeper at night.
Update: I could add animation to the first part. The second part graph looks ugly though. I need to play more with this stuff. This is amazing stuff!
Here’s the code for Livemd version: https://github.com/code-shoily/advent_of_code/blob/master/lib/2022/day_12.livemd
And the regular one (This one has cache based part 2 since I don’t need to worry about any graphs) advent_of_code/lib/2022/day_12.ex at master · code-shoily/advent_of_code · GitHub
maennchen
I initially went with :digraph, since that is already part of the BEAM:
I felt that that was cheating since that did 90% of the given problem. Therefore, I also implemented Dijkstra naively based on the Wikipedia description of the algorithm.
kwando
:digraph was the thing I was looking for, I knew there were something like that already bundled but didn’t remember the name ![]()
Is there a way to get “all the distances” (djikstra table) from :digraph? It was quick enough to :digraph.get_short_path(g, <a>, goal) for every a though…
stevensonmt
Like most I used :digraph. Felt like cheating actually. Though after Day 11 I wasn’t above taking the easy path.
Pun intended.
AOCDay12
defmodule Day12 do
defmodule Input do
def sample_data() do
"""
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi
"""
end
@neighbors [{-1, 0}, {1, 0}, {0, -1}, {0, 1}]
def parse(input) do
rows =
input
|> String.split("\n", trim: true)
{row_len, col_len} = {length(rows), String.length(hd(rows))}
rows
|> populate_vertices()
|> populate_edges(row_len - 1, col_len - 1)
end
defp populate_vertices(input_rows) do
input_rows
|> Enum.with_index()
|> Enum.reduce({:digraph.new(), nil, nil}, fn {line, r}, {graph, start, stop} ->
String.codepoints(line)
|> Enum.with_index()
|> Enum.reduce({graph, start, stop}, fn {<<val::utf8>>, c}, {g, begin, final} ->
case val do
83 ->
:digraph.add_vertex(g, {c, r}, ?a)
{g, {c, r}, final}
69 ->
:digraph.add_vertex(g, {c, r}, ?z)
{g, begin, {c, r}}
_ ->
:digraph.add_vertex(g, {c, r}, val)
{g, begin, final}
end
end)
end)
end
defp populate_edges({graph, start, stop}, rows, cols) do
edged_graph =
for r <- 0..rows,
c <- 0..cols,
reduce: graph do
graff ->
{emanating_vertex, v1} = :digraph.vertex(graff, {c, r})
@neighbors
|> Enum.reduce(graff, fn {x, y}, gr ->
incident = :digraph.vertex(graff, {c + x, r + y})
case incident do
{incident_vertex, v2} when v2 - v1 == 1 ->
:digraph.add_edge(gr, emanating_vertex, incident_vertex)
gr
{incident_vertex, v2} when v2 - v1 <= 0 ->
:digraph.add_edge(gr, emanating_vertex, incident_vertex)
gr
_ ->
gr
end
end)
end
{edged_graph, start, stop}
end
end
def part1({graph, start, stop}) do
:digraph.get_short_path(graph, start, stop) |> Enum.count() |> Kernel.-(1)
end
def part2({graph, _start, stop}) do
graph
|> :digraph.vertices()
|> Enum.filter(fn vertex -> {vertex, ?a} == :digraph.vertex(graph, vertex) end)
|> Enum.map(fn starter -> :digraph.get_short_path(graph, starter, stop) end)
|> Enum.filter(& &1)
|> Enum.map(fn path -> Enum.count(path) - 1 end)
|> Enum.min()
end
milli
I don’t know if the algorithm I’ve used has a name, but part 2 took ~20 ms on my machine ![]()
defp walk([{position, distance} | rest], map) do
if map[position].distance == :unknown do
new_map = put_in(map, [position, :distance], distance)
possible_ways = where_can_i_go(position, map) |> Enum.map(fn p -> {p, distance + 1} end)
walk(rest ++ possible_ways, new_map)
else
walk(rest, map)
end
end








