Current section

Files

Jump to
bitgraph lib traversal dfs.ex
Raw

lib/traversal/dfs.ex

defmodule BitGraph.Dfs do
alias BitGraph.E
alias BitGraph.Array
@moduledoc """
Depth-first search.
Implementation roughly follows
https://cp-algorithms.com/graph/depth-first-search.html
"""
@white_vertex 0
@gray_vertex 1
@black_vertex 2
def run(graph, vertices \\ :all, opts \\ [])
def run(graph, :all, opts) do
run(graph, BitGraph.vertex_indices(graph), opts)
end
def run(graph, root, opts) when is_integer(root) do
run(graph, [root], opts)
end
def run(graph, vertices, opts) when is_list(vertices) do
initial_state = Keyword.get(opts, :state) || init_dfs(graph, hd(vertices), opts)
Enum.reduce(vertices, initial_state, fn vertex, state_acc ->
state_acc = Map.put(state_acc, :root, vertex)
if vertex_color(state_acc, vertex) == @white_vertex do
dfs_impl(graph, vertex, state_acc)
else
state_acc
end
end)
end
defp init_dfs(graph, root, opts) do
num_vertices = BitGraph.num_vertices(graph)
%{
root: root,
direction: Keyword.get(opts, :direction, :forward),
reduce_fun: Keyword.get(opts, :reduce_fun, default_reduce_fun())
|> normalize_reduce_fun(),
dag: true,
timer: :counters.new(1, [:atomics]),
## Color (white, black or gray)
#### white if the vertex hasn't been visited,
#### black, if the vertex was visited
#### gray, if dfs has already exited the vertex.
####
color: Array.new(num_vertices),
## Time of dfs entering the vertex
time_in: Array.new(num_vertices),
## Time of dfs exiting vertex
time_out: Array.new(num_vertices),
parent: Array.new(num_vertices),
acc: nil
}
end
defp dfs_impl(graph, vertex, %{direction: direction} = state)
when is_integer(vertex) do
time = inc_timer(state)
Array.put(state[:time_in], vertex, time)
Array.put(state[:color], vertex, @gray_vertex)
{_action, initial_state} = apply_reduce(state, vertex)
Enum.reduce_while(vertex_neighbors(graph, vertex, direction), initial_state, fn
neighbor, state_acc ->
Array.put(state[:parent], neighbor, vertex)
case vertex_color(state, neighbor) do
@black_vertex -> state_acc
@gray_vertex ->
on_loop(neighbor, state_acc)
@white_vertex ->
dfs_impl(graph, neighbor, state_acc)
end
|> to_reduce_while_result()
end)
|> tap(fn _ ->
time = inc_timer(state)
Array.put(state[:time_out], vertex, time)
Array.put(state[:color], vertex, @black_vertex)
end)
end
defp inc_timer(%{timer: timer} = _state) do
:ok = :counters.add(timer, 1, 1)
:counters.get(timer, 1)
end
def vertex_color(state, vertex) do
Array.get(state[:color], vertex)
end
defp default_reduce_fun() do
fn %{acc: _acc} = _state, _vertex, _loop? ->
nil
end
end
defp normalize_reduce_fun(reduce_fun) when is_function(reduce_fun, 2) do
fn state, vertex, _loop? -> reduce_fun.(state, vertex) end
end
defp normalize_reduce_fun(reduce_fun) when is_function(reduce_fun, 3) do
reduce_fun
end
defp apply_reduce(%{reduce_fun: reduce_fun} = state, vertex, opts \\ []) when is_function(reduce_fun, 3) do
{next_action, acc} = case reduce_fun.(state, vertex, Keyword.get(opts, :loop, false)) do
{:next, acc} -> {:next, acc}
{:stop, acc} -> {:stop, acc}
acc -> {:next, acc}
end
{next_action, Map.put(state, :acc, acc)}
end
defp to_reduce_while_result(result) do
case result do
{:next, acc} -> {:cont, acc}
{:stop, acc} -> {:halt, acc}
acc -> {:cont, acc}
end
end
defp vertex_neighbors(graph, vertex, :forward) do
E.out_neighbors(graph, vertex)
end
defp vertex_neighbors(graph, vertex, :reverse) do
E.in_neighbors(graph, vertex)
end
defp vertex_neighbors(graph, vertex, :both) do
MapSet.union(vertex_neighbors(graph, vertex, :forward),
vertex_neighbors(graph, vertex, :reverse))
end
def acyclic?(state) do
state.dag
end
## The vertex has closed the loop
defp on_loop(vertex, state) do
Map.put(state, :dag, false)
|> apply_reduce(vertex, loop: true)
end
def order(state, :in, order) when order in [:desc, :asc] do
order_impl(state[:time_in], order)
end
def order(state, :out, order) when order in [:desc, :asc] do
order_impl(state[:time_out], order)
end
defp order_impl(arr_ref, order) do
arr_ref
|> Array.to_list()
|> Enum.with_index(1)
|> Enum.sort(order)
|> Enum.map(fn {_time_out, vertex_idx} -> vertex_idx end)
end
end