Skip to content

Commit

Permalink
stdlib: Fix preorder and postorder traversals in digraph_utils
Browse files Browse the repository at this point in the history
The previous `digraph_utils:preorder/1` and `digraph_utils:postorder/1`
did not start the traversal from root nodes. This fix makes both
traversals only start or restart from a root node in one of the
components, or an arbitrary node if no root node can be visited.
Since in the previous version, the search can start and restart from
arbitrary nodes, this fix should not break backwards compatibility.

It is worth noting that the digraph does not have a notion of a left or
right subtree. The result of both traversals may not be the same after
relabelling vertices.
  • Loading branch information
lucioleKi committed Dec 10, 2024
1 parent 3749221 commit f1f456c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 36 deletions.
72 changes: 38 additions & 34 deletions lib/stdlib/src/digraph_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ collecting visited vertices in preorder.
Vertices :: [digraph:vertex()].

preorder(G) ->
lists:reverse(revpreorder(G)).
T = sets:new(),
{_, Acc} = ptraverse(roots(G), fun out/3, G, T, [], []),
lists:reverse(lists:append(Acc)).

-doc """
Returns all vertices of digraph `Digraph`. The order is given by a
Expand All @@ -405,66 +407,68 @@ vertices.
Vertices :: [digraph:vertex()].

postorder(G) ->
lists:reverse(revpostorder(G)).
T = sets:new(),
{Acc, _} = posttraverse(roots(G), G, T, []),
lists:reverse(Acc).

%%
%% Local functions
%%

roots(G) ->
R1 = [V || V <- digraph:vertices(G), digraph:in_degree(G, V) =:= 0],
R2 = [X || [X|_] <- components(G)],
R1 ++ R2.

forest(G, SF) ->
forest(G, SF, digraph:vertices(G)).

forest(G, SF, Vs) ->
forest(G, SF, Vs, first).

forest(G, SF, Vs, HandleFirst) ->
T = ets:new(forest, [set]),
F = fun(V, LL) -> pretraverse(HandleFirst, V, SF, G, T, LL) end,
LL = lists:foldl(F, [], Vs),
ets:delete(T),
T = sets:new(),
F = fun(V, {T0, LL}) -> pretraverse(HandleFirst, V, SF, G, T0, LL) end,
{_, LL} = lists:foldl(F, {T, []}, Vs),
LL.

pretraverse(first, V, SF, G, T, LL) ->
ptraverse([V], SF, G, T, [], LL);
pretraverse(not_first, V, SF, G, T, LL) ->
case ets:member(T, V) of
case sets:is_element(V, T) of
false -> ptraverse(SF(G, V, []), SF, G, T, [], LL);
true -> LL
true -> {T, LL}
end.

ptraverse([V | Vs], SF, G, T, Rs, LL) ->
case ets:member(T, V) of
ptraverse([V | Vs], SF, G, T0, Rs, LL) ->
case sets:is_element(V, T0) of
false ->
ets:insert(T, {V}),
ptraverse(SF(G, V, Vs), SF, G, T, [V | Rs], LL);
T1 = sets:add_element(V, T0),
ptraverse(SF(G, V, Vs), SF, G, T1, [V | Rs], LL);
true ->
ptraverse(Vs, SF, G, T, Rs, LL)
ptraverse(Vs, SF, G, T0, Rs, LL)
end;
ptraverse([], _SF, _G, _T, [], LL) ->
LL;
ptraverse([], _SF, _G, _T, Rs, LL) ->
[Rs | LL].

revpreorder(G) ->
lists:append(forest(G, fun out/3)).
ptraverse([], _SF, _G, T, [], LL) ->
{T, LL};
ptraverse([], _SF, _G, T, Rs, LL) ->
{T, [Rs | LL]}.

revpostorder(G) ->
T = ets:new(forest, [set]),
L = posttraverse(digraph:vertices(G), G, T, []),
ets:delete(T),
T = sets:new(),
{L, _} = posttraverse(digraph:vertices(G), G, T, []),
L.

posttraverse([V | Vs], G, T, L) ->
L1 = case ets:member(T, V) of
false ->
ets:insert(T, {V}),
[V | posttraverse(out(G, V, []), G, T, L)];
true ->
L
end,
posttraverse(Vs, G, T, L1);
posttraverse([], _G, _T, L) ->
L.
posttraverse([V | Vs], G, T0, Acc0) ->
case sets:is_element(V, T0) of
false ->
T1 = sets:add_element(V, T0),
{Acc1, T2} = posttraverse(out(G, V, []), G, T1, Acc0),
posttraverse(Vs, G, T2, [V|Acc1]);
true ->
posttraverse(Vs, G, T0, Acc0)
end;
posttraverse([], _G, T, Acc) ->
{Acc, T}.

in(G, V, Vs) ->
digraph:in_neighbours(G, V) ++ Vs.
Expand Down
20 changes: 18 additions & 2 deletions lib/stdlib/test/digraph_utils_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
init_per_group/2,end_per_group/2]).

-export([simple/1, loop/1, isolated/1, topsort/1, subgraph/1,
condensation/1, tree/1]).
condensation/1, tree/1, traversals/1]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand All @@ -39,7 +39,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].

all() ->
[simple, loop, isolated, topsort, subgraph,
condensation, tree].
condensation, tree, traversals].

groups() ->
[].
Expand Down Expand Up @@ -248,6 +248,22 @@ tree(Config) when is_list(Config) ->

ok.

%% OTP-9040
traversals(Config) when is_list(Config) ->
G = digraph:new([]),
[] = digraph_utils:preorder(G),
[] = digraph_utils:postorder(G),
add_edges(G, [{a,b},{b,c},{c,d},{d,e}]),
[a,b,c,d,e] = digraph_utils:preorder(G),
[e,d,c,b,a] = digraph_utils:postorder(G),
add_edges(G, [{0,1},{1,2},{2,0}]),
[a,b,c,d,e,0,1,2] = digraph_utils:preorder(G),
[e,d,c,b,a,2,1,0] = digraph_utils:postorder(G),
add_edges(G, [{x,0},{y,1},{z,2}]),
[z,2,0,1,y,x,a,b,c,d,e] = digraph_utils:preorder(G),
[1,0,2,z,y,x,e,d,c,b,a] = digraph_utils:postorder(G),
ok.

is_tree(Es) ->
is_tree([], Es).

Expand Down

0 comments on commit f1f456c

Please sign in to comment.