Skip to content

Commit

Permalink
Added batch_topsort/1
Browse files Browse the repository at this point in the history
  • Loading branch information
akoutmos authored and bitwalker committed Mar 10, 2024
1 parent 76052b3 commit 460cdfd
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 2 deletions.
36 changes: 35 additions & 1 deletion lib/graph.ex
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ defmodule Graph do
NOTE: Currently this function assumes graphs are directed graphs, but in the future
it will support undirected graphs as well.
NOTE 2: To avoid to overwrite vertices with the same label, output is
NOTE 2: To avoid to overwrite vertices with the same label, output is
generated using the internal numeric ID as vertex label.
Original label is expressed as `id[label="<label>"]`.
Expand Down Expand Up @@ -1494,6 +1494,40 @@ defmodule Graph do
def topsort(%__MODULE__{type: :undirected}), do: false
def topsort(%__MODULE__{} = g), do: Graph.Directed.topsort(g)

@doc """
Returns a batch topological ordering of the vertices of graph `g`, if such an ordering exists, otherwise it
returns false. For each vertex in the returned list, no out-neighbors occur earlier in the list. This differs
from `topsort/1` in that this function returns a list of lists where each sublist can be concurrently evaluated
without worrying about elements in the sublist depending on eachother.
Multiple edges between two vertices are considered a single edge for purposes of this sort.
## Example
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}])
...> Graph.batch_topsort(g)
[[:a], [:b, :c]]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
...> Graph.batch_topsort(g)
[[:a], [:b], [:c], [:d]]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :x, :y, :z])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:c, :d}, {:x, :y}, {:x, :z}])
...> Graph.batch_topsort(g)
[[:a, :x], [:b, :c, :y, :z], [:d]]
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :x, :y, :z])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}, {:c, :a}])
...> Graph.batch_topsort(g)
false
"""
@spec batch_topsort(t) :: [vertex] | false
def batch_topsort(%__MODULE__{type: :undirected}), do: false
def batch_topsort(%__MODULE__{} = g), do: Graph.Directed.batch_topsort(g)

@doc """
Returns a list of connected components, where each component is a list of vertices.
Expand Down
36 changes: 36 additions & 0 deletions lib/graph/directed.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@ 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)

Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ defmodule Graph.Mixfile do
{:stream_data, "~> 0.5", only: [:test]},
{:excoveralls, "~> 0.7", only: [:test]},
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
{:ex_doc, ">= 0.0.0", only: :dev}
{:ex_doc, ">= 0.0.0", only: :dev},
{:ssl_verify_fun, "~> 1.1", manager: :rebar3, only: [:test], override: true}
]
end

Expand Down

0 comments on commit 460cdfd

Please sign in to comment.