123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186(*
MIT License
Copyright (c) 2019 Daniil Baturin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*)(* CHANGES:
Added functor [Hashtbl] argument since we need to inject custom comparators + hashes.
Removed [sort_strongly_connected_components] since unused
and made [Hashtbl] functor argument monomorphic over integers.
Went further and removed everything except [sort]. *)moduleMake(Hashtbl:Hashtbl.S)=struct(* Original: https://github.com/dmbaturin/ocaml-tsort/blob/39320d506369b1dc508ca75ee4f8898e401f9016/src/lib/compat.ml *)moduleCompat=struct(*
Basic functions not available in older versions of OCaml's standard
library.
*)moduleHashtbl=structletfind_opttblkey=trySome(Hashtbl.findtblkey)withNot_found->Noneletlist_keystbl=Hashtbl.fold(funk_acc->k::acc)tbl[]endmoduleList=structletrecremove?(eq=(=))~keyxs=matchxswith|[]->[]|x::xs->ifeqxkeythenxselsex::remove~eq~keyxsendend(* Original: https://github.com/dmbaturin/ocaml-tsort/blob/39320d506369b1dc508ca75ee4f8898e401f9016/src/lib/tsort.ml *)(* User-friendly topological sort based on Kahn's algorithm.
Usage example: sort [("foundation", []); ("basement", ["foundation"]);]
Authors: Daniil Baturin (2019), Martin Jambon (2020).
*)type'asort_result=Sortedof'alist|ErrorCycleof'alist(* Deduplicate list items.
Differences with CCList.uniq:
- when an item is duplicated, keep the first item encountered rather than
the last: [1;2;3;1] gives [1;2;3] (not [2;3;1]).
- complexity is O(n), not O(n^2).
*)letdeduplicatel=lettbl=Hashtbl.create(List.lengthl)inList.fold_left(funaccx->ifHashtbl.memtblxthenaccelse(Hashtbl.addtblx();x::acc))[]l|>List.revletgraph_hash_of_listl=letupdatehkv=letorig_v=Compat.Hashtbl.find_opthkinmatchorig_vwith|None->Hashtbl.addhkv|Someorig_v->(* Allow "partial" dependency lists like [(1, [2]); (1, [3]); (2, [1])].
Sometimes it's a more natural way to write cyclic graphs.
*)Hashtbl.replacehk(List.appendorig_vv)inlettbl=Hashtbl.create100inlet()=List.iter(fun(k,v)->updatetblkv)linlet()=Hashtbl.filter_map_inplace(fun_xs->Some(deduplicatexs))tblintbl(* Finds "isolated" nodes,
that is, nodes that have no dependencies *)letfind_isolated_nodeshash=letauxiddepsacc=matchdepswith[]->id::acc|_->accinHashtbl.foldauxhash[](* Takes a node name list and removes all those nodes from a hash *)letremove_nodesnodeshash=List.iter(Hashtbl.removehash)nodes(* Walks through a node:dependencies hash and removes a dependency
from all nodes that have it in their dependency lists *)letremove_dependencyhashdep=letauxdephashid=letdeps=Hashtbl.findhashidinletdeps=ifList.exists((=)dep)depsthenCompat.List.remove~eq:(=)~key:depdepselsedepsinbeginHashtbl.removehashid;Hashtbl.addhashiddepsendinletids=Compat.Hashtbl.list_keyshashinList.iter(auxdephash)ids(*
Append missing nodes to the graph, in the order in which they were
encountered. This particular order doesn't have to be guaranteed by the
API but seems nice to have.
*)letadd_missing_nodesgraph_lgraph=letmissing=List.fold_left(funacc(_,vl)->List.fold_left(funaccv->ifnot(Hashtbl.memgraphv)then(v,[])::accelseacc)accvl)[]graph_l|>List.revinList.iter(fun(v,vl)->Hashtbl.replacegraphvvl)missing;graph_l@missing(* The Kahn's algorithm:
1. Find nodes that have no dependencies ("isolated") and remove them from
the graph hash.
Add them to the initial sorted nodes list and the list of isolated
nodes for the first sorting pass.
2. For every isolated node, walk through the remaining nodes and
remove it from their dependency list.
Nodes that only depended on it now have empty dependency lists.
3. Find all nodes with empty dependency lists and append them to the sorted
nodes list _and_ the list of isolated nodes to use for the next step
4. Repeat until the list of isolated nodes is empty
5. If the graph hash is still not empty, it means there is a cycle.
*)letsortnodes=letrecsorting_loopdepshashacc=matchdepswith|[]->acc|dep::deps->let()=remove_dependencyhashdepinletisolated_nodes=find_isolated_nodeshashinlet()=remove_nodesisolated_nodeshashinsorting_loop(List.appenddepsisolated_nodes)hash(List.appendaccisolated_nodes)inletnodes_hash=graph_hash_of_listnodesinlet_nodes=add_missing_nodesnodesnodes_hashinletbase_nodes=find_isolated_nodesnodes_hashinlet()=remove_nodesbase_nodesnodes_hashinletsorted_node_ids=sorting_loopbase_nodesnodes_hash[]inletsorted_node_ids=List.appendbase_nodessorted_node_idsinletremaining_ids=Compat.Hashtbl.list_keysnodes_hashinmatchremaining_idswith|[]->Sortedsorted_node_ids|_->ErrorCycleremaining_idsend