add containers.scc

This commit is contained in:
Simon Cruanes 2022-10-05 10:12:50 -04:00
parent 5b1f2af227
commit e0a8285e17
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
4 changed files with 145 additions and 6 deletions

View file

@ -26,8 +26,15 @@ Containers is:
`Containers` (intended to be opened, replaces some stdlib modules `Containers` (intended to be opened, replaces some stdlib modules
with extended ones), and a small S-expression printer and parser with extended ones), and a small S-expression printer and parser
that can be functorized over the representation of values. that can be functorized over the representation of values.
- Utilities around the `unix` library in `containers.unix` (mainly to spawn - Some sub-libraries with a specific focus each:
* Utilities around the `unix` library in `containers.unix` (mainly to spawn
sub-processes easily and deal with resources safely) sub-processes easily and deal with resources safely)
* A bencode codec in `containers.bencode`. This is a tiny json-like
serialization format that is extremely simple. It comes from bittorrent files.
* A [CBOR](https://cbor.io) codec in `containers.cbor`. This is a
compact binary serialization format.
* The [Strongly Connected Component](https://en.wikipedia.org/wiki/Strongly_connected_component)
algorithm, functorized, in `containers.scc`
- A separate library `containers-data` with additional - A separate library `containers-data` with additional
data structures that don't have an equivalent in the standard library, data structures that don't have an equivalent in the standard library,
typically not as thoroughly maintained. This is now in its own package typically not as thoroughly maintained. This is now in its own package
@ -35,10 +42,6 @@ Containers is:
- A separate library for threaded programming in `containers-thread`, - A separate library for threaded programming in `containers-thread`,
including a blocking queue, semaphores, an extension of `Mutex`, and including a blocking queue, semaphores, an extension of `Mutex`, and
thread-pool based futures. This is in its own package since 3.0. thread-pool based futures. This is in its own package since 3.0.
- A bencode codec in `containers.bencode`. This is a tiny json-like
serialization format that is extremely simple. It comes from bittorrent files.
- A [CBOR](https://cbor.io) codec in `containers.cbor`. This is a
compact binary serialization format.
Some of the modules have been moved to their own repository (e.g. `sequence` (now `iter`), Some of the modules have been moved to their own repository (e.g. `sequence` (now `iter`),
`gen`, `qcheck`) and are on opam for great fun and profit. `gen`, `qcheck`) and are on opam for great fun and profit.

104
src/scc/containers_scc.ml Normal file
View file

@ -0,0 +1,104 @@
type 'a iter = ('a -> unit) -> unit
module type ARG = sig
type t
type node
val children : t -> node -> node iter
module Node_tbl : Hashtbl.S with type key = node
end
module type S = sig
module A : ARG
val scc : A.t -> A.node list -> A.node list list
end
module Make (A : ARG) = struct
module A = A
type state = {
mutable min_id: int; (* min ID of the vertex' scc *)
id: int; (* ID of the vertex *)
mutable on_stack: bool;
vertex: A.node;
}
let mk_cell v n = { min_id = n; id = n; on_stack = false; vertex = v }
(* pop elements of [stack] until we reach node with given [id] *)
let rec pop_down_to ~id acc stack =
assert (not (Stack.is_empty stack));
let cell = Stack.pop stack in
cell.on_stack <- false;
if cell.id = id then (
assert (cell.id = cell.min_id);
cell.vertex :: acc (* return SCC *)
) else
pop_down_to ~id (cell.vertex :: acc) stack
let scc (graph : A.t) (nodes : A.node list) : _ list list =
let res = ref [] in
let tbl = A.Node_tbl.create 16 in
(* stack of nodes being explored, for the DFS *)
let to_explore = Stack.create () in
(* stack for Tarjan's algorithm itself *)
let stack = Stack.create () in
(* unique ID for new nodes *)
let n = ref 0 in
(* exploration starting from [v] *)
let explore_from (v : A.node) : unit =
Stack.push (`Enter v) to_explore;
while not (Stack.is_empty to_explore) do
match Stack.pop to_explore with
| `Enter v ->
if not (A.Node_tbl.mem tbl v) then (
(* remember unique ID for [v] *)
let id = !n in
incr n;
let cell = mk_cell v id in
cell.on_stack <- true;
A.Node_tbl.add tbl v cell;
Stack.push cell stack;
Stack.push (`Exit (v, cell)) to_explore;
(* explore children *)
let children = A.children graph v in
children (fun v' -> Stack.push (`Enter v') to_explore)
)
| `Exit (v, cell) ->
(* update [min_id] *)
assert cell.on_stack;
let children = A.children graph v in
children (fun dest ->
(* must not fail, [dest] already explored *)
let dest_cell = A.Node_tbl.find tbl dest in
(* same SCC? yes if [dest] points to [cell.v] *)
if dest_cell.on_stack then
cell.min_id <- min cell.min_id dest_cell.min_id);
(* pop from stack if SCC found *)
if cell.id = cell.min_id then (
let scc = pop_down_to ~id:cell.id [] stack in
res := scc :: !res
)
done
in
List.iter explore_from nodes;
assert (Stack.is_empty stack);
!res
end
let scc (type graph node) ~(tbl : (module Hashtbl.S with type key = node))
~graph ~children ~nodes () : _ list =
let module S = Make (struct
type t = graph
type nonrec node = node
let children = children
module Node_tbl = (val tbl)
end) in
S.scc graph nodes

View file

@ -0,0 +1,26 @@
type 'a iter = ('a -> unit) -> unit
module type ARG = sig
type t
type node
val children : t -> node -> node iter
module Node_tbl : Hashtbl.S with type key = node
end
module type S = sig
module A : ARG
val scc : A.t -> A.node list -> A.node list list
end
module Make (A : ARG) : S with module A = A
val scc :
tbl:(module Hashtbl.S with type key = 'node) ->
graph:'graph ->
children:('graph -> 'node -> 'node iter) ->
nodes:'node list ->
unit ->
'node list list

6
src/scc/dune Normal file
View file

@ -0,0 +1,6 @@
(library
(name containers_scc)
(public_name containers.scc)
(synopsis "strongly connected components algorithm")
(libraries containers))