mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 11:15:31 -05:00
implemented LazyGraph.Dot.pp (and pp_enum to have more control); LazyGraph.map and filter implemented too
455 lines
16 KiB
OCaml
455 lines
16 KiB
OCaml
(*
|
|
Copyright (c) 2013, Simon Cruanes
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer. Redistributions in binary
|
|
form must reproduce the above copyright notice, this list of conditions and the
|
|
following disclaimer in the documentation and/or other materials provided with
|
|
the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*)
|
|
|
|
(** {1 Lazy graph data structure} *)
|
|
|
|
module type S = sig
|
|
(** This module serves to represent directed graphs in a lazy fashion. Such
|
|
a graph is always accessed from a given initial node (so only connected
|
|
components can be represented by a single value of type ('v,'e) t). *)
|
|
|
|
(** {2 Type definitions} *)
|
|
|
|
type vertex
|
|
(** The concrete type of a vertex. Vertices are considered unique within
|
|
the graph. *)
|
|
|
|
module H : Hashtbl.S with type key = vertex
|
|
|
|
type ('v, 'e) t = vertex -> ('v, 'e) node
|
|
(** Lazy graph structure. Vertices are annotated with values of type 'v,
|
|
and edges are of type 'e. A graph is a function that maps vertices
|
|
to a label and some edges to other vertices. *)
|
|
and ('v, 'e) node =
|
|
| Empty
|
|
| Node of vertex * 'v * ('e * vertex) Enum.t
|
|
(** A single node of the graph, with outgoing edges *)
|
|
and 'e path = (vertex * 'e * vertex) list
|
|
|
|
(** {2 Basic constructors} *)
|
|
|
|
(** It is difficult to provide generic combinators to build graphs. The problem
|
|
is that if one wants to "update" a node, it's still very hard to update
|
|
how other nodes re-generate the current node at the same time. *)
|
|
|
|
val empty : ('v, 'e) t
|
|
(** Empty graph *)
|
|
|
|
val singleton : vertex -> 'v -> ('v, 'e) t
|
|
(** Trivial graph, composed of one node *)
|
|
|
|
val from_enum : vertices:(vertex * 'v) Enum.t ->
|
|
edges:(vertex * 'e * vertex) Enum.t ->
|
|
('v, 'e) t
|
|
(** Concrete (eager) representation of a Graph *)
|
|
|
|
val from_fun : (vertex -> ('v * ('e * vertex) list) option) -> ('v, 'e) t
|
|
(** Convenient semi-lazy implementation of graphs *)
|
|
|
|
(** {2 Traversals} *)
|
|
|
|
(** {3 Full interface to traversals} *)
|
|
module Full : sig
|
|
type ('v, 'e) traverse_event =
|
|
| EnterVertex of vertex * 'v * int * 'e path (* unique ID, trail *)
|
|
| ExitVertex of vertex (* trail *)
|
|
| MeetEdge of vertex * 'e * vertex * edge_type (* edge *)
|
|
and edge_type =
|
|
| EdgeForward (* toward non explored vertex *)
|
|
| EdgeBackward (* toward the current trail *)
|
|
| EdgeTransverse (* toward a totally explored part of the graph *)
|
|
|
|
val bfs_full : ?id:int ref -> ?explored:unit H.t ->
|
|
('v, 'e) t -> vertex Enum.t -> ('v, 'e) traverse_event Enum.t
|
|
(** Lazy traversal in breadth first from a finite set of vertices *)
|
|
|
|
val dfs_full : ?id:int ref -> ?explored:unit H.t ->
|
|
('v, 'e) t -> vertex Enum.t -> ('v, 'e) traverse_event Enum.t
|
|
(** Lazy traversal in depth first from a finite set of vertices *)
|
|
end
|
|
|
|
(** The traversal functions assign a unique ID to every traversed node *)
|
|
|
|
val bfs : ?id:int ref -> ?explored:unit H.t ->
|
|
('v, 'e) t -> vertex -> (vertex * 'v * int) Enum.t
|
|
(** Lazy traversal in breadth first *)
|
|
|
|
val dfs : ?id:int ref -> ?explored:unit H.t ->
|
|
('v, 'e) t -> vertex -> (vertex * 'v * int) Enum.t
|
|
(** Lazy traversal in depth first *)
|
|
|
|
val enum : ('v, 'e) t -> vertex -> (vertex * 'v) Enum.t * (vertex * 'e * vertex) Enum.t
|
|
(** Convert to an enumeration. The traversal order is undefined. *)
|
|
|
|
val depth : (_, 'e) t -> vertex -> (int, 'e) t
|
|
(** Map vertices to their depth, ie their distance from the initial point *)
|
|
|
|
val min_path : ?distance:(vertex -> 'e -> vertex -> int) ->
|
|
('v, 'e) t -> vertex -> vertex ->
|
|
int * 'e path
|
|
(** Minimal path from the given Graph from the first vertex to
|
|
the second. It returns both the distance and the path *)
|
|
|
|
(** {2 Lazy transformations} *)
|
|
|
|
val union : ?combine:('v -> 'v -> 'v) -> ('v, 'e) t -> ('v, 'e) t -> ('v, 'e) t
|
|
(** Lazy union of the two graphs. If they have common vertices,
|
|
[combine] is used to combine the labels. By default, the second
|
|
label is dropped and only the first is kept *)
|
|
|
|
val map : vertices:('v -> 'v2) -> edges:('e -> 'e2) ->
|
|
('v, 'e) t -> ('v2, 'e2) t
|
|
(** Map vertice and edge labels *)
|
|
|
|
val filter : ?vertices:(vertex -> 'v -> bool) ->
|
|
?edges:(vertex -> 'e -> vertex -> bool) ->
|
|
('v, 'e) t -> ('v, 'e) t
|
|
(** Filter out vertices and edges that do not satisfy the given
|
|
predicates. The default predicates always return true. *)
|
|
|
|
val limit_depth : max:int -> ('v, 'e) t -> ('v, 'e) t
|
|
(** Return the same graph, but with a bounded depth. Vertices whose
|
|
depth is too high will be replaced by Empty *)
|
|
|
|
module Infix : sig
|
|
val (++) : ('v, 'e) t -> ('v, 'e) t -> ('v, 'e) t
|
|
(** Union of graphs (alias for {! union}) *)
|
|
end
|
|
|
|
(** {2 Pretty printing in the DOT (graphviz) format *)
|
|
module Dot : sig
|
|
type attribute = [
|
|
| `Color of string
|
|
| `Shape of string
|
|
| `Weight of int
|
|
| `Style of string
|
|
| `Label of string
|
|
| `Other of string * string
|
|
] (** Dot attribute *)
|
|
|
|
val pp_enum : name:string -> Format.formatter ->
|
|
(attribute list,attribute list) Full.traverse_event Enum.t ->
|
|
unit
|
|
|
|
val pp : name:string -> (attribute list, attribute list) t ->
|
|
Format.formatter ->
|
|
vertex Enum.t -> unit
|
|
(** Pretty print the given graph (starting from the given set of vertices)
|
|
to the channel in DOT format *)
|
|
end
|
|
end
|
|
|
|
(** {2 Module type for hashable types} *)
|
|
module type HASHABLE = sig
|
|
type t
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
end
|
|
|
|
(** {2 Implementation of HASHABLE with physical equality and hash} *)
|
|
module PhysicalHash(X : sig type t end) : HASHABLE with type t = X.t
|
|
= struct
|
|
type t = X.t
|
|
let equal a b = a == b
|
|
let hash a = Hashtbl.hash a
|
|
end
|
|
|
|
(** {2 Build a graph} *)
|
|
module Make(X : HASHABLE) : S with type vertex = X.t = struct
|
|
(** {2 Type definitions} *)
|
|
|
|
type vertex = X.t
|
|
(** The concrete type of a vertex. Vertices are considered unique within
|
|
the graph. *)
|
|
|
|
module H = Hashtbl.Make(X)
|
|
|
|
type ('v, 'e) t = vertex -> ('v, 'e) node
|
|
(** Lazy graph structure. Vertices are annotated with values of type 'v,
|
|
and edges are of type 'e. A graph is a function that maps vertices
|
|
to a label and some edges to other vertices. *)
|
|
and ('v, 'e) node =
|
|
| Empty
|
|
| Node of vertex * 'v * ('e * vertex) Enum.t
|
|
(** A single node of the graph, with outgoing edges *)
|
|
and 'e path = (vertex * 'e * vertex) list
|
|
|
|
(** {2 Basic constructors} *)
|
|
|
|
let empty =
|
|
fun _ -> Empty
|
|
|
|
let singleton v label =
|
|
fun v' ->
|
|
if X.equal v v' then Node (v, label, Enum.empty) else Empty
|
|
|
|
let from_enum ~vertices ~edges = failwith "from_enum: not implemented"
|
|
|
|
let from_fun f =
|
|
fun v ->
|
|
match f v with
|
|
| None -> Empty
|
|
| Some (l, edges) -> Node (v, l, Enum.of_list edges)
|
|
|
|
(** {2 Traversals} *)
|
|
|
|
(** {3 Full interface to traversals} *)
|
|
module Full = struct
|
|
type ('v, 'e) traverse_event =
|
|
| EnterVertex of vertex * 'v * int * 'e path (* unique ID, trail *)
|
|
| ExitVertex of vertex (* trail *)
|
|
| MeetEdge of vertex * 'e * vertex * edge_type (* edge *)
|
|
and edge_type =
|
|
| EdgeForward (* toward non explored vertex *)
|
|
| EdgeBackward (* toward the current trail *)
|
|
| EdgeTransverse (* toward a totally explored part of the graph *)
|
|
|
|
(* helper type *)
|
|
type 'e todo_item =
|
|
| FullEnter of vertex * 'e path
|
|
| FullExit of vertex
|
|
| FullFollowEdge of 'e path
|
|
|
|
(** Is [v] part of the [path]? *)
|
|
let rec mem_path path v =
|
|
match path with
|
|
| (v',_,v'')::path' ->
|
|
(X.equal v v') || (X.equal v v'') || (mem_path path' v)
|
|
| [] -> false
|
|
|
|
let bfs_full ?(id=ref 0) ?(explored=H.create 5) graph vertices =
|
|
fun () ->
|
|
let q = Queue.create () in (* queue of nodes to explore *)
|
|
Enum.iter (fun v -> Queue.push (FullEnter (v,[])) q) vertices;
|
|
let rec next () =
|
|
if Queue.is_empty q then raise Enum.EOG else
|
|
match Queue.pop q with
|
|
| FullEnter (v', path) ->
|
|
if H.mem explored v' then next ()
|
|
else begin match graph v' with
|
|
| Empty -> next ()
|
|
| Node (_, label, edges) ->
|
|
H.add explored v' ();
|
|
(* explore neighbors *)
|
|
Enum.iter
|
|
(fun (e,v'') ->
|
|
let path' = (v'',e,v') :: path in
|
|
Queue.push (FullFollowEdge path') q)
|
|
edges;
|
|
(* exit node afterward *)
|
|
Queue.push (FullExit v') q;
|
|
(* return this vertex *)
|
|
let i = !id in
|
|
incr id;
|
|
EnterVertex (v', label, i, path)
|
|
end
|
|
| FullExit v' -> ExitVertex v'
|
|
| FullFollowEdge [] -> assert false
|
|
| FullFollowEdge (((v'', e, v') :: path) as path') ->
|
|
(* edge path .... v' --e--> v'' *)
|
|
if H.mem explored v''
|
|
then if mem_path path v''
|
|
then MeetEdge (v'', e, v', EdgeBackward)
|
|
else MeetEdge (v'', e, v', EdgeTransverse)
|
|
else begin
|
|
(* explore this edge *)
|
|
Queue.push (FullEnter (v'', path')) q;
|
|
MeetEdge (v'', e, v', EdgeForward)
|
|
end
|
|
in next
|
|
|
|
let dfs_full ?(id=ref 0) ?(explored=H.create 5) graph vertices =
|
|
fun () ->
|
|
let s = Stack.create () in (* stack of nodes to explore *)
|
|
Enum.iter (fun v -> Stack.push (FullEnter (v,[])) s) vertices;
|
|
let rec next () =
|
|
if Stack.is_empty s then raise Enum.EOG else
|
|
match Stack.pop s with
|
|
| FullExit v' -> ExitVertex v'
|
|
| FullEnter (v', path) ->
|
|
if H.mem explored v' then next ()
|
|
(* explore the node now *)
|
|
else begin match graph v' with
|
|
| Empty -> next ()
|
|
| Node (_, label, edges) ->
|
|
H.add explored v' ();
|
|
(* prepare to exit later *)
|
|
Stack.push (FullExit v') s;
|
|
(* explore neighbors *)
|
|
Enum.iter
|
|
(fun (e,v'') ->
|
|
Stack.push (FullFollowEdge ((v'', e, v') :: path)) s)
|
|
edges;
|
|
(* return this vertex *)
|
|
let i = !id in
|
|
incr id;
|
|
EnterVertex (v', label, i, path)
|
|
end
|
|
| FullFollowEdge [] -> assert false
|
|
| FullFollowEdge (((v'', e, v') :: path) as path') ->
|
|
(* edge path .... v' --e--> v'' *)
|
|
if H.mem explored v''
|
|
then if mem_path path v''
|
|
then MeetEdge (v'', e, v', EdgeBackward)
|
|
else MeetEdge (v'', e, v', EdgeTransverse)
|
|
else begin
|
|
(* explore this edge *)
|
|
Stack.push (FullEnter (v'', path')) s;
|
|
MeetEdge (v'', e, v', EdgeForward)
|
|
end
|
|
in next
|
|
end
|
|
|
|
let bfs ?id ?explored graph v =
|
|
Enum.filterMap
|
|
(function
|
|
| Full.EnterVertex (v, l, i, _) -> Some (v, l, i)
|
|
| _ -> None)
|
|
(Full.bfs_full ?id ?explored graph (Enum.singleton v))
|
|
|
|
let dfs ?id ?explored graph v =
|
|
Enum.filterMap
|
|
(function
|
|
| Full.EnterVertex (v, l, i, _) -> Some (v, l, i)
|
|
| _ -> None)
|
|
(Full.dfs_full ?id ?explored graph (Enum.singleton v))
|
|
|
|
let enum graph v = (Enum.empty, Enum.empty) (* TODO *)
|
|
|
|
let depth graph v =
|
|
failwith "not implemented" (* TODO *)
|
|
|
|
(** Minimal path from the given Graph from the first vertex to
|
|
the second. It returns both the distance and the path *)
|
|
let min_path ?(distance=fun v1 e v2 -> 1) graph v1 v2 =
|
|
failwith "not implemented"
|
|
|
|
(** {2 Lazy transformations} *)
|
|
|
|
let union ?(combine=fun x y -> x) g1 g2 =
|
|
fun v ->
|
|
match g1 v, g2 v with
|
|
| Empty, Empty -> Empty
|
|
| ((Node _) as n), Empty -> n
|
|
| Empty, ((Node _) as n) -> n
|
|
| Node (_, l1, e1), Node (_, l2, e2) ->
|
|
Node (v, combine l1 l2, Enum.append e1 e2)
|
|
|
|
let map ~vertices ~edges g =
|
|
fun vertex ->
|
|
match g vertex with
|
|
| Empty -> Empty
|
|
| Node (_, l, edges_enum) ->
|
|
let edges_enum' = Enum.map (fun (e,v') -> (edges e), v') edges_enum in
|
|
Node (vertex, vertices l, edges_enum')
|
|
|
|
let filter ?(vertices=(fun v l -> true)) ?(edges=fun v1 e v2 -> true) g =
|
|
fun vertex ->
|
|
match g vertex with
|
|
| Empty -> Empty
|
|
| Node (_, l, edges_enum) when vertices vertex l ->
|
|
(* filter out edges *)
|
|
let edges_enum' = Enum.filter (fun (e,v') -> edges vertex e v') edges_enum in
|
|
Node (vertex, l, edges_enum')
|
|
| Node _ -> Empty (* filter out this vertex *)
|
|
|
|
let limit_depth ~max g =
|
|
(* TODO; this should be eager (compute depth by BFS) *)
|
|
failwith "not implemented"
|
|
|
|
module Infix = struct
|
|
let (++) g1 g2 = union ?combine:None g1 g2
|
|
end
|
|
|
|
module Dot = struct
|
|
type attribute = [
|
|
| `Color of string
|
|
| `Shape of string
|
|
| `Weight of int
|
|
| `Style of string
|
|
| `Label of string
|
|
| `Other of string * string
|
|
] (** Dot attribute *)
|
|
|
|
(** Print an enum of Full.traverse_event *)
|
|
let pp_enum ~name formatter events =
|
|
(* print an attribute *)
|
|
let print_attribute formatter attr =
|
|
match attr with
|
|
| `Color c -> Format.fprintf formatter "color=%s" c
|
|
| `Shape s -> Format.fprintf formatter "shape=%s" s
|
|
| `Weight w -> Format.fprintf formatter "weight=%d" w
|
|
| `Style s -> Format.fprintf formatter "style=%s" s
|
|
| `Label l -> Format.fprintf formatter "label=\"%s\"" l
|
|
| `Other (name, value) -> Format.fprintf formatter "%s=\"%s\"" name value
|
|
(* map from vertices to integers *)
|
|
and get_id =
|
|
let count = ref 0 in
|
|
let m = H.create 5 in
|
|
fun vertex ->
|
|
try H.find m vertex
|
|
with Not_found ->
|
|
let n = !count in
|
|
incr count;
|
|
H.replace m vertex n;
|
|
n
|
|
in
|
|
(* the unique name of a vertex *)
|
|
let pp_vertex formatter v =
|
|
Format.fprintf formatter "vertex_%d" (get_id v) in
|
|
(* print preamble *)
|
|
Format.fprintf formatter "@[<v2>digraph %s {@;" name;
|
|
(* traverse *)
|
|
Enum.iter
|
|
(function
|
|
| Full.EnterVertex (v, attrs, _, _) ->
|
|
Format.fprintf formatter " @[<h>%a [%a];@]@." pp_vertex v
|
|
(Enum.pp ~sep:"," print_attribute) (Enum.of_list attrs)
|
|
| Full.ExitVertex _ -> ()
|
|
| Full.MeetEdge (v2, attrs, v1, _) ->
|
|
Format.fprintf formatter " @[<h>%a -> %a [%a];@]@."
|
|
pp_vertex v1 pp_vertex v2
|
|
(Enum.pp ~sep:"," print_attribute)
|
|
(Enum.of_list attrs))
|
|
events;
|
|
(* close *)
|
|
Format.fprintf formatter "}@]@;@?";
|
|
()
|
|
|
|
let pp ~name graph formatter vertices =
|
|
let enum = Full.bfs_full graph vertices in
|
|
pp_enum ~name formatter enum
|
|
end
|
|
end
|
|
|
|
(** {2 Build a graph based on physical equality} *)
|
|
module PhysicalMake(X : sig type t end) : S with type vertex = X.t
|
|
= Make(PhysicalHash(X))
|
|
|
|
module IntGraph = Make(struct
|
|
type t = int
|
|
let equal i j = i = j
|
|
let hash i = i
|
|
end)
|