123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403(*
Topological sort that doesn't give up on cycles:
A --> B
C --> D gives: [A] [B C] [D]
B --> C
C --> B
The complexity of this algorithm is in O(N^2), although it is plenty
fast for the application.
TODO: use the Kosaraju–Sharir algorithm which is simpler and has a linear
complexity. A good description of the algorithm is given here:
http://scienceblogs.com/goodmath/2007/10/30/computing-strongly-connected-c/
*)openImportmoduletypeParam=sigtypettypeidvalid:t->id(* for error messages and debugging *)valto_string:id->stringendmoduleMake(P:Param)=structtypeid=P.idtypenode_state=Visited|Unvisited(* graph node with mutable labels *)typenode={id:P.id;value:P.t;mutablestate:node_state;}(* all edges of the original graph *)typegraph={forward:(id,nodelist)Hashtbl.t;backward:(id,nodelist)Hashtbl.t;}(* subset of nodes on which iteration and set operations are possible
(intersection, union, etc.) *)moduleS=Set.Make(structtypet=nodeletcompareab=comparea.idb.idend)letdebug=reffalseletprint_nodesmsgnodes=if!debugthenprintf"%s: %s\n%!"msg(String.concat" "(List.map(funv->P.to_stringv.id)(S.elementsnodes)))(*
Algorithm outline:
Input: directed graph
Output: a list of node groups sorted topologically, i.e.
for any group A coming after group B and any node n_a in A
and any node n_b in B, there is no edge
going from n_b to n_a.
... such that the number of groups is maximum.
Initialization:
Build graph structure such that allows following edges both forward
and backward.
1. root and leaf elimination: a leaf is a node without outgoing edges,
a root is a node without incoming edges.
2. partitioning into strict ancestors (left), cycle (middle),
and strict descendants (right), and other (independent):
pick an processed node V (our pivot), determine the set of
descendant nodes and the set of ancestor nodes by following edges
from V respectively forward and backward.
Nodes that belong both to the descendant set
and to the ancestor set form a cycle with V and are removed
from the graph.
Strict ancestors are sorted starting from step 1, strict descendants
are sorted starting from step 1.
*)letget_neighborsvedges=tryHashtbl.findedgesv.idwithNot_found->[]letfiltered_neighborsvedgesgraph_nodes=letall=get_neighborsvedgesinList.filter(funneighbor->S.memneighborgraph_nodes)allletpick_onenodes=tryletv=S.choosenodesinSome(v,S.removevnodes)withNot_found->Noneletadd_listsetl=List.fold_left(funsetv->S.addvset)setlletis_rootback_edgesgraph_nodesv=filtered_neighborsvback_edgesgraph_nodes=[]leteliminate_roots_recursivelyedgesback_edgesnodes=letrecauxsortedgraph_nodesinput_nodes=matchpick_oneinput_nodeswith|None->List.rev_map(funv->false,S.singletonv)sorted,graph_nodes|Some(v,input_nodes)->ifis_rootback_edgesgraph_nodesvthenletsorted=v::sortedinletchildren=filtered_neighborsvedgesgraph_nodesinletgraph_nodes=S.removevgraph_nodesinletinput_nodes=add_listinput_nodeschildreninassert(not(S.memvinput_nodes));auxsortedgraph_nodesinput_nodeselseauxsortedgraph_nodesinput_nodesinaux[]nodesnodesleteliminate_rootsgraphnodes=eliminate_roots_recursivelygraph.forwardgraph.backwardnodesleteliminate_leavesgraphnodes=letsorted_leaves,remaining_nodes=eliminate_roots_recursivelygraph.backwardgraph.forwardnodesinremaining_nodes,List.revsorted_leaves(*
Collect all nodes reachable from the root.
Exclude the root unless it can be reached by some cycle.
*)letvisitedgesstart_nodenodes=assert(S.for_all(funv->v.state=Unvisited)nodes);letvisited=ref[]inletmark_visitedv=v.state<-Visited;visited:=v::!visitedinletclear_visited()=List.iter(funv->v.state<-Unvisited)!visitedinletreccoloraccv=matchv.statewith|Visited->acc|Unvisited->mark_visitedv;List.fold_left(funaccneighbor->ifS.memneighbornodesthenletacc=S.addneighboraccincoloraccneighborelseacc)acc(get_neighborsvedges)inletvisited_excluding_root=colorS.emptystart_nodeinclear_visited();visited_excluding_rootletfind_descendantsgraphpivotnodes=print_nodes"find_descendants"nodes;visitgraph.forwardpivotnodesletfind_ancestorsgraphpivotnodes=print_nodes"find_ancestors"nodes;visitgraph.backwardpivotnodesletrecsort_subgraphgraphnodes=print_nodes"sort_subgraph"nodes;letsorted_left,nodes=eliminate_rootsgraphnodesinletnodes,sorted_right=eliminate_leavesgraphnodesinletsorted_middle=matchpick_onenodeswith|None->[]|Some(pivot,_)->partitiongraphpivotnodesinsorted_left@sorted_middle@sorted_rightandpartitiongraphpivotnodes=print_nodes"partition"nodes;let(-)=S.diffinletancestors=find_ancestorsgraphpivotnodesinletdescendants=find_descendantsgraphpivotnodesinletstrict_ancestors=ancestors-descendantsinletstrict_descendants=descendants-ancestorsinletcycle=S.interdescendantsancestorsinletis_cyclic,pivot_group=ifS.is_emptycyclethen(assert(not(S.mempivotancestors));assert(not(S.mempivotdescendants));false,S.singletonpivot)else(assert(S.mempivotcycle);true,cycle)inletother=nodes-pivot_group-strict_ancestors-strict_descendantsinprint_nodes"ancestors"ancestors;print_nodes"descendants"descendants;print_nodes"cycle"cycle;print_nodes"other"other;sort_subgraphgraphstrict_ancestors@[is_cyclic,pivot_group]@sort_subgraphgraphstrict_descendants@sort_subgraphgraphother(* could as well be inserted anywhere *)(* Data preparation and cleanup *)letsortl=letnode_tbl=Hashtbl.create(2*List.lengthl)inletmake_nodex=letid=P.idxinifnot(Hashtbl.memnode_tblid)thenletv={id;state=Unvisited;value=x;}inHashtbl.addnode_tblidvinletget_nodeid=trySome(Hashtbl.findnode_tblid)withNot_found->Noneinletmake_edgeedgesv1v2=letl=tryHashtbl.findedgesv1.idwithNot_found->[]inHashtbl.replaceedgesv1.id(v2::l)inList.iter(fun(x,_)->make_nodex)l;letforward=Hashtbl.create(2*List.lengthl)inletbackward=Hashtbl.create(2*List.lengthl)inList.iter(fun(x1,l)->letv1=matchget_node(P.idx1)with|Somev->v|None->assertfalseinList.iter(funid2->matchget_nodeid2with|None->()|Somev2->make_edgeforwardv1v2;make_edgebackwardv2v1;)l)l;letgraph={forward;backward}inletnodes=Hashtbl.fold(fun_vset->S.addvset)node_tblS.emptyinletsorted_groups=sort_subgraphgraphnodesin(* Export as lists *)List.map(fun(is_cyclic,set)->is_cyclic,List.map(funnode->node.value)(S.elementsset))sorted_groupsend(* Testing *)moduleSorter=Make(structtypet=inttypeid=intletidx=xletto_stringx=string_of_intxend)letrecin_orderresultab=matchresultwith|[]->false|(_,l)::ll->ifList.memblthenfalseelseifList.memalthenList.exists(fun(_,l)->List.membl)llelsein_orderllabletrecin_same_cycleresultab=matchresultwith|[]->false|(cyclic,l)::ll->cyclic&&List.memal&&List.membl||in_same_cyclellabletnot_in_cycleresultx=List.exists(function|(false,[y])wheny=x->true|_->false)resultletseqresultab=in_orderresultab&¬(in_orderresultba)&¬(in_same_cycleresultab)letcycresultab=in_same_cycleresultab&¬(in_orderresultab)&¬(in_orderresultba)letsngresultx=not_in_cycleresultxletrun_test1()=Sorter.sort[1,[2];2,[3];3,[1];]lettest1()=letr=run_test1()inassert(cycr12);assert(cycr23);assert(cycr13)letrun_test2()=Sorter.sort[1,[2];2,[3];3,[];5,[6];4,[5];6,[];]lettest2()=letr=run_test2()inassert(seqr12);assert(seqr23);assert(seqr45);assert(seqr56);assert(sngr3);assert(sngr6)letrun_test3()=Sorter.sort[1,[2;3];2,[3];3,[3;4];4,[3;];5,[6];6,[6;1];5,[7];7,[8];8,[9];9,[0];10,[10];11,[12];12,[13];13,[11];]lettest3()=letr=run_test3()inassert(not(sngr0));assert(not(seqr01));assert(not(seqr10));assert(not(cycr00));assert(sngr1);assert(seqr12);assert(seqr14);assert(seqr13);assert(seqr23);assert(cycr34);assert(sngr5);assert(seqr61);assert(sngr7);assert(sngr8);assert(sngr9);assert(seqr59);assert(cycr1010);assert(cycr1112);assert(cycr1213);assert(cycr1113)lettest()=test1();test2();test3()