Current section

Files

Jump to
hologram lib libgraph graph directed.ex
Raw

lib/libgraph/graph/directed.ex

defmodule Graph.Directed do
@moduledoc false
@compile {:inline, [in_neighbors: 2, in_neighbors: 3, out_neighbors: 2, out_neighbors: 3]}
def batch_topsort(%Graph{} = g) do
if is_acyclic?(g) do
g
|> topsort()
|> do_batch_topsort([], g)
else
false
end
end
defp do_batch_topsort([], acc, %Graph{}) do
acc
end
defp do_batch_topsort([next_vertex | rest_verticies], [], %Graph{} = g) do
do_batch_topsort(rest_verticies, [[next_vertex]], g)
end
defp do_batch_topsort([next_vertex | rest_verticies], acc, %Graph{} = g) do
batch_index =
Enum.find_index(acc, fn vertex_batch ->
Enum.all?(vertex_batch, fn check_vertex ->
Graph.dijkstra(g, check_vertex, next_vertex) == nil
end)
end)
updated_acc =
if not is_nil(batch_index) do
List.update_at(acc, batch_index, fn vertex_batch -> [next_vertex | vertex_batch] end)
else
List.insert_at(acc, -1, [next_vertex])
end
do_batch_topsort(rest_verticies, updated_acc, g)
end
def topsort(%Graph{vertices: vs} = g) do
l = reverse_postorder(g)
if length(forest(g, &in_neighbors/3, l)) == map_size(vs) do
Enum.map(l, &Map.get(vs, &1))
else
false
end
end
def preorder(%Graph{vertices: vs} = g) do
g
|> reverse_preorder()
|> Stream.map(fn id -> Map.get(vs, id) end)
|> Enum.reverse()
end
def postorder(%Graph{vertices: vs} = g) do
g
|> reverse_postorder()
|> Stream.map(fn id -> Map.get(vs, id) end)
|> Enum.reverse()
end
def is_arborescence?(%Graph{} = g) do
arborescence_root(g) != nil
end
def arborescence_root(%Graph{vertices: vs, out_edges: oe} = g) do
num_edges = Enum.reduce(oe, 0, fn {_, out}, sum -> sum + MapSet.size(out) end)
num_vertices = map_size(vs)
if num_edges == num_vertices - 1 do
[root] =
Enum.reduce(vs, [], fn {v_id, v}, acc ->
case length(in_neighbors(g, v_id)) do
1 -> acc
0 when acc == [] -> [v]
end
end)
root
else
nil
end
catch
_type, _err ->
nil
end
def is_acyclic?(%Graph{} = g) do
has_loops?(g) == false and topsort(g) != false
end
def has_loops?(%Graph{vertices: vs} = g) do
for {v_id, _} <- vs do
if is_reflexive_vertex(g, v_id) do
throw(:has_loop)
end
end
false
catch
_, :has_loop ->
true
end
def loop_vertices(%Graph{vertices: vs} = g) do
for {v_id, v} <- vs, is_reflexive_vertex(g, v_id), do: v
end
def components(%Graph{vertices: vs} = g) do
for component <- forest(g, &inout/3) do
for id <- component, do: Map.get(vs, id)
end
end
def strong_components(%Graph{vertices: vs} = g) do
for component <- forest(g, &in_neighbors/3, reverse_postorder(g)) do
for id <- component, do: Map.get(vs, id)
end
end
def reachable(%Graph{vertices: vertices, vertex_identifier: vertex_identifier} = g, vs)
when is_list(vs) do
vs = Enum.map(vs, vertex_identifier)
for id <- :lists.append(forest(g, &out_neighbors/3, vs, :first)), do: Map.get(vertices, id)
end
def reachable_neighbors(
%Graph{vertices: vertices, vertex_identifier: vertex_identifier} = g,
vs
)
when is_list(vs) do
vs = Enum.map(vs, vertex_identifier)
for id <- :lists.append(forest(g, &out_neighbors/3, vs, :not_first)),
do: Map.get(vertices, id)
end
def reaching(%Graph{vertices: vertices, vertex_identifier: vertex_identifier} = g, vs)
when is_list(vs) do
vs = Enum.map(vs, vertex_identifier)
for id <- :lists.append(forest(g, &in_neighbors/3, vs, :first)), do: Map.get(vertices, id)
end
def reaching_neighbors(%Graph{vertices: vertices, vertex_identifier: vertex_identifier} = g, vs)
when is_list(vs) do
vs = Enum.map(vs, vertex_identifier)
for id <- :lists.append(forest(g, &in_neighbors/3, vs, :not_first)), do: Map.get(vertices, id)
end
def in_neighbors(%Graph{} = g, v, []) do
in_neighbors(g, v)
end
def in_neighbors(%Graph{in_edges: ie}, v, vs) do
case Map.get(ie, v) do
nil -> vs
v_in -> MapSet.to_list(v_in) ++ vs
end
end
def in_neighbors(%Graph{in_edges: ie}, v) do
case Map.get(ie, v) do
nil -> []
v_in -> MapSet.to_list(v_in)
end
end
def out_neighbors(%Graph{} = g, v, []) do
out_neighbors(g, v)
end
def out_neighbors(%Graph{out_edges: oe}, v, vs) do
case Map.get(oe, v) do
nil -> vs
v_out -> MapSet.to_list(v_out) ++ vs
end
end
def out_neighbors(%Graph{out_edges: oe}, v) do
case Map.get(oe, v) do
nil -> []
v_out -> MapSet.to_list(v_out)
end
end
## Private
defp is_reflexive_vertex(g, v) do
Enum.member?(out_neighbors(g, v), v)
end
defp forest(%Graph{vertices: vs} = g, fun) do
forest(g, fun, Map.keys(vs))
end
defp forest(g, fun, vs) do
forest(g, fun, vs, :first)
end
defp forest(g, fun, vs, handle_first) do
{_, acc} =
List.foldl(vs, {MapSet.new(), []}, fn v, {visited, acc} ->
pretraverse(handle_first, v, fun, g, visited, acc)
end)
acc
end
defp pretraverse(:first, v, fun, g, visited, acc) do
ptraverse([v], fun, g, visited, [], acc)
end
defp pretraverse(:not_first, v, fun, g, visited, acc) do
if MapSet.member?(visited, v) do
{visited, acc}
else
ptraverse(fun.(g, v, []), fun, g, visited, [], acc)
end
end
defp ptraverse([v | vs], fun, g, visited, results, acc) do
if MapSet.member?(visited, v) do
ptraverse(vs, fun, g, visited, results, acc)
else
visited = MapSet.put(visited, v)
ptraverse(fun.(g, v, vs), fun, g, visited, [v | results], acc)
end
end
defp ptraverse([], _fun, _g, visited, [], acc), do: {visited, acc}
defp ptraverse([], _fun, _g, visited, results, acc), do: {visited, [results | acc]}
defp reverse_preorder(g) do
:lists.append(forest(g, &out_neighbors/3))
end
defp reverse_postorder(%Graph{vertices: vs} = g) do
{_, l} = posttraverse(Map.keys(vs), g, MapSet.new(), [])
l
end
defp posttraverse([v | vs], g, visited, acc) do
{visited, acc} =
if MapSet.member?(visited, v) do
{visited, acc}
else
visited = MapSet.put(visited, v)
{visited2, acc2} = posttraverse(out_neighbors(g, v, []), g, visited, acc)
{visited2, [v | acc2]}
end
posttraverse(vs, g, visited, acc)
end
defp posttraverse([], _g, visited, acc), do: {visited, acc}
defp inout(g, v, vs) do
in_neighbors(g, v, out_neighbors(g, v, vs))
end
end