Packages
mneme
0.3.0-rc.1
0.10.2
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.9.0-alpha.1
0.9.0-alpha.0
0.8.2
0.8.1
0.8.0
0.7.0
0.6.1
0.6.0
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.3.0-rc.1
0.3.0-rc.0
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Snapshot testing tool using familiar assertions
Current section
Files
Jump to
Current section
Files
lib/mneme/diff/pathfinding.ex
# Derived from bitwalker/libgraph:
# https://github.com/bitwalker/libgraph/blob/main/lib/graph/pathfinding.ex
defmodule Mneme.Diff.Pathfinding do
@moduledoc false
@doc """
Finds the shortest path between `v` and a target vertex.
The given `next_fun` receives the vertex under consideration and must
return `{:cont, neighbors}` to continue searching or `:halt` to
indicate that the target has been found.
"""
def lazy_dijkstra(v, vertex_identifier, next_fun) do
lazy_a_star(v, vertex_identifier, next_fun, fn _v -> 0 end)
end
@doc false
def lazy_a_star(v, vertex_identifier, next_fun, hfun) do
v_id = vertex_identifier.(v)
known = %{v_id => v}
tree = Graph.new(vertex_identifier: &Function.identity/1) |> Graph.add_vertex(v_id)
q = PriorityQueue.new()
with {:cont, vs_out} <- next_fun.(v) do
{vs_out, known} = push_known(known, vs_out, vertex_identifier)
q
|> push_vertices(known, v_id, vs_out, hfun)
|> do_lazy_bfs(known, tree, vertex_identifier, next_fun, hfun)
|> case do
{:ok, known, path} -> for(id <- path, do: Map.get(known, id))
:error -> nil
end
else
:halt -> [v]
:error -> nil
end
end
## Private
defp do_lazy_bfs(q, known, tree, vertex_identifier, next_fun, hfun) do
case PriorityQueue.pop(q) do
{{:value, {v1_id, v2_id, acc_cost}}, q} ->
v2 = Map.fetch!(known, v2_id)
case next_fun.(v2) do
:halt ->
{:ok, known, construct_path(v1_id, tree, [v2_id])}
{:cont, vs_out} ->
if Map.has_key?(tree.vertices, v2_id) do
do_lazy_bfs(q, known, tree, vertex_identifier, next_fun, hfun)
else
{v2_out, known} = push_known(known, vs_out, vertex_identifier)
tree =
tree
|> Graph.add_vertex(v2_id)
|> Graph.add_edge(v2_id, v1_id)
q
|> push_vertices(known, v2_id, v2_out, hfun, acc_cost)
|> do_lazy_bfs(known, tree, vertex_identifier, next_fun, hfun)
end
end
{:empty, _} ->
:error
end
end
defp push_known(known, vs_out, vertex_identifier) do
Enum.map_reduce(vs_out, known, fn {v, cost}, known ->
id = vertex_identifier.(v)
{{id, cost}, Map.put(known, id, v)}
end)
end
defp push_vertices(q, known, v_id, vs_out, hfun, acc_cost \\ 0) do
Enum.reduce(vs_out, q, fn {v2_id, cost}, q ->
edge_cost = acc_cost + cost
q_cost = edge_cost + hfun.(Map.get(known, v2_id))
PriorityQueue.push(q, {v_id, v2_id, edge_cost}, q_cost)
end)
end
defp construct_path(v_id, %Graph{out_edges: oe} = tree, path) do
path = [v_id | path]
case oe |> Map.get(v_id, MapSet.new()) |> MapSet.to_list() do
[] -> path
[next_id] -> construct_path(next_id, tree, path)
end
end
end