Skip to content

Commit 460cdfd

Browse files
akoutmosbitwalker
authored andcommitted
Added batch_topsort/1
1 parent 76052b3 commit 460cdfd

3 files changed

Lines changed: 73 additions & 2 deletions

File tree

lib/graph.ex

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ defmodule Graph do
135135
NOTE: Currently this function assumes graphs are directed graphs, but in the future
136136
it will support undirected graphs as well.
137137
138-
NOTE 2: To avoid to overwrite vertices with the same label, output is
138+
NOTE 2: To avoid to overwrite vertices with the same label, output is
139139
generated using the internal numeric ID as vertex label.
140140
Original label is expressed as `id[label="<label>"]`.
141141
@@ -1494,6 +1494,40 @@ defmodule Graph do
14941494
def topsort(%__MODULE__{type: :undirected}), do: false
14951495
def topsort(%__MODULE__{} = g), do: Graph.Directed.topsort(g)
14961496

1497+
@doc """
1498+
Returns a batch topological ordering of the vertices of graph `g`, if such an ordering exists, otherwise it
1499+
returns false. For each vertex in the returned list, no out-neighbors occur earlier in the list. This differs
1500+
from `topsort/1` in that this function returns a list of lists where each sublist can be concurrently evaluated
1501+
without worrying about elements in the sublist depending on eachother.
1502+
1503+
Multiple edges between two vertices are considered a single edge for purposes of this sort.
1504+
1505+
## Example
1506+
1507+
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c])
1508+
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}])
1509+
...> Graph.batch_topsort(g)
1510+
[[:a], [:b, :c]]
1511+
1512+
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
1513+
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
1514+
...> Graph.batch_topsort(g)
1515+
[[:a], [:b], [:c], [:d]]
1516+
1517+
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :x, :y, :z])
1518+
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:c, :d}, {:x, :y}, {:x, :z}])
1519+
...> Graph.batch_topsort(g)
1520+
[[:a, :x], [:b, :c, :y, :z], [:d]]
1521+
1522+
iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :x, :y, :z])
1523+
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}, {:c, :a}])
1524+
...> Graph.batch_topsort(g)
1525+
false
1526+
"""
1527+
@spec batch_topsort(t) :: [vertex] | false
1528+
def batch_topsort(%__MODULE__{type: :undirected}), do: false
1529+
def batch_topsort(%__MODULE__{} = g), do: Graph.Directed.batch_topsort(g)
1530+
14971531
@doc """
14981532
Returns a list of connected components, where each component is a list of vertices.
14991533

lib/graph/directed.ex

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@ defmodule Graph.Directed do
22
@moduledoc false
33
@compile {:inline, [in_neighbors: 2, in_neighbors: 3, out_neighbors: 2, out_neighbors: 3]}
44

5+
def batch_topsort(%Graph{} = g) do
6+
if is_acyclic?(g) do
7+
g
8+
|> topsort()
9+
|> do_batch_topsort([], g)
10+
else
11+
false
12+
end
13+
end
14+
15+
defp do_batch_topsort([], acc, %Graph{}) do
16+
acc
17+
end
18+
19+
defp do_batch_topsort([next_vertex | rest_verticies], [], %Graph{} = g) do
20+
do_batch_topsort(rest_verticies, [[next_vertex]], g)
21+
end
22+
23+
defp do_batch_topsort([next_vertex | rest_verticies], acc, %Graph{} = g) do
24+
batch_index =
25+
Enum.find_index(acc, fn vertex_batch ->
26+
Enum.all?(vertex_batch, fn check_vertex ->
27+
Graph.dijkstra(g, check_vertex, next_vertex) == nil
28+
end)
29+
end)
30+
31+
updated_acc =
32+
if not is_nil(batch_index) do
33+
List.update_at(acc, batch_index, fn vertex_batch -> [next_vertex | vertex_batch] end)
34+
else
35+
List.insert_at(acc, -1, [next_vertex])
36+
end
37+
38+
do_batch_topsort(rest_verticies, updated_acc, g)
39+
end
40+
541
def topsort(%Graph{vertices: vs} = g) do
642
l = reverse_postorder(g)
743

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ defmodule Graph.Mixfile do
5252
{:stream_data, "~> 0.5", only: [:test]},
5353
{:excoveralls, "~> 0.7", only: [:test]},
5454
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
55-
{:ex_doc, ">= 0.0.0", only: :dev}
55+
{:ex_doc, ">= 0.0.0", only: :dev},
56+
{:ssl_verify_fun, "~> 1.1", manager: :rebar3, only: [:test], override: true}
5657
]
5758
end
5859

0 commit comments

Comments
 (0)