mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 19:25:28 -05:00
324 lines
7.8 KiB
OCaml
324 lines
7.8 KiB
OCaml
(* This file is free software, part of containers. See file "license" for more details. *)
|
|
|
|
(** {1 Lazy Tree Structure}
|
|
This structure can be used to represent trees and directed
|
|
graphs (as infinite trees) in a lazy fashion. It
|
|
is a structural type. *)
|
|
|
|
type 'a iter = ('a -> unit) -> unit
|
|
type 'a gen = unit -> 'a option
|
|
type 'a printer = Format.formatter -> 'a -> unit
|
|
type +'a t = unit -> [ `Nil | `Node of 'a * 'a t list ]
|
|
|
|
let empty () = `Nil
|
|
|
|
let is_empty t =
|
|
match t () with
|
|
| `Nil -> true
|
|
| `Node _ -> false
|
|
|
|
let singleton x () = `Node (x, [])
|
|
let node x l () = `Node (x, l)
|
|
let node1 x t () = `Node (x, [ t ])
|
|
let node2 x t1 t2 () = `Node (x, [ t1; t2 ])
|
|
|
|
let rec fold f acc t =
|
|
match t () with
|
|
| `Nil -> acc
|
|
| `Node (x, l) ->
|
|
let acc = f acc x in
|
|
List.fold_left (fold f) acc l
|
|
|
|
let rec iter f t =
|
|
match t () with
|
|
| `Nil -> ()
|
|
| `Node (x, l) ->
|
|
f x;
|
|
List.iter (iter f) l
|
|
|
|
let size t = fold (fun n _ -> n + 1) 0 t
|
|
|
|
let height t =
|
|
let rec aux t k =
|
|
match t () with
|
|
| `Nil -> k 0
|
|
| `Node (_, l) -> aux_l 0 l k
|
|
and aux_l acc l k =
|
|
match l with
|
|
| [] -> k acc
|
|
| t' :: l' -> aux t' (fun n -> aux_l (max acc n) l' k)
|
|
in
|
|
aux t (fun x -> x)
|
|
|
|
let rec map f t () =
|
|
match t () with
|
|
| `Nil -> `Nil
|
|
| `Node (x, l) -> `Node (f x, List.map (map f) l)
|
|
|
|
let ( >|= ) t f = map f t
|
|
|
|
let rec cut_depth n t () =
|
|
match t () with
|
|
| `Nil -> `Nil
|
|
| `Node _ when n = 0 -> `Nil
|
|
| `Node (x, l) -> `Node (x, List.map (cut_depth (n - 1)) l)
|
|
|
|
(** {2 Graph Traversals} *)
|
|
|
|
(** Abstract Set structure *)
|
|
class type ['a] pset =
|
|
object
|
|
method add : 'a -> 'a pset
|
|
method mem : 'a -> bool
|
|
end
|
|
|
|
let set_of_cmp (type elt) ~cmp () =
|
|
let module S = Set.Make (struct
|
|
type t = elt
|
|
|
|
let compare = cmp
|
|
end) in
|
|
object
|
|
val s = S.empty
|
|
method add x = {<s = S.add x s>}
|
|
method mem x = S.mem x s
|
|
end
|
|
|
|
let _nil () = Seq.Nil
|
|
let _cons x l = Seq.Cons (x, l)
|
|
|
|
let dfs ~pset t =
|
|
let rec dfs pset stack () =
|
|
match stack with
|
|
| [] -> Seq.Nil
|
|
| `Explore t :: stack' ->
|
|
(match t () with
|
|
| `Nil -> dfs pset stack' ()
|
|
| `Node (x, _) when pset#mem x -> dfs pset stack' () (* loop *)
|
|
| `Node (x, l) ->
|
|
let pset' = pset#add x in
|
|
let stack' =
|
|
List.rev_append
|
|
(List.rev_map (fun x -> `Explore x) l)
|
|
(`Exit x :: stack')
|
|
in
|
|
_cons (`Enter x) (dfs pset' stack'))
|
|
| `Exit x :: stack' -> _cons (`Exit x) (dfs pset stack')
|
|
in
|
|
dfs pset [ `Explore t ]
|
|
|
|
(** Functional queues for BFS *)
|
|
module FQ = struct
|
|
type 'a t = { hd: 'a list; tl: 'a list }
|
|
|
|
exception Empty
|
|
|
|
(* invariant: if hd=[], then tl=[] *)
|
|
let _make hd tl =
|
|
match hd with
|
|
| [] -> { hd = List.rev tl; tl = [] }
|
|
| _ :: _ -> { hd; tl }
|
|
|
|
let empty = _make [] []
|
|
|
|
let list_is_empty = function
|
|
| [] -> true
|
|
| _ :: _ -> false
|
|
|
|
let is_empty q = list_is_empty q.hd
|
|
let push q x = _make q.hd (x :: q.tl)
|
|
|
|
let pop_exn q =
|
|
match q.hd with
|
|
| [] ->
|
|
assert (list_is_empty q.tl);
|
|
raise Empty
|
|
| x :: hd' ->
|
|
let q' = _make hd' q.tl in
|
|
x, q'
|
|
end
|
|
|
|
let bfs ~pset t =
|
|
let rec bfs pset q () =
|
|
if FQ.is_empty q then
|
|
Seq.Nil
|
|
else (
|
|
let t, q' = FQ.pop_exn q in
|
|
match t () with
|
|
| `Nil -> bfs pset q' ()
|
|
| `Node (x, _) when pset#mem x -> bfs pset q' () (* loop *)
|
|
| `Node (x, l) ->
|
|
let q' = List.fold_left FQ.push q' l in
|
|
let pset' = pset#add x in
|
|
_cons x (bfs pset' q')
|
|
)
|
|
in
|
|
bfs pset (FQ.push FQ.empty t)
|
|
|
|
let rec force t : [ `Nil | `Node of 'a * 'b list ] as 'b =
|
|
match t () with
|
|
| `Nil -> `Nil
|
|
| `Node (x, l) -> `Node (x, List.map force l)
|
|
|
|
let find ~pset f t =
|
|
let rec _find_kl f l =
|
|
match l () with
|
|
| Seq.Nil -> None
|
|
| Seq.Cons (x, l') ->
|
|
(match f x with
|
|
| None -> _find_kl f l'
|
|
| Some _ as res -> res)
|
|
in
|
|
_find_kl f (bfs ~pset t)
|
|
|
|
(** {2 Pretty-printing} *)
|
|
|
|
let pp pp_x fmt t =
|
|
(* at depth [lvl] *)
|
|
let rec pp fmt t =
|
|
match t with
|
|
| `Nil -> ()
|
|
| `Node (x, children) ->
|
|
let children = filter children in
|
|
(match children with
|
|
| [] -> pp_x fmt x
|
|
| _ :: _ ->
|
|
Format.fprintf fmt "@[<v2>(@[<hov0>%a@]%a)@]" pp_x x pp_children
|
|
children)
|
|
and filter l =
|
|
let l =
|
|
List.fold_left
|
|
(fun acc c ->
|
|
match c () with
|
|
| `Nil -> acc
|
|
| `Node _ as sub -> sub :: acc)
|
|
[] l
|
|
in
|
|
List.rev l
|
|
and pp_children fmt children =
|
|
(* remove empty children *)
|
|
List.iter
|
|
(fun c ->
|
|
Format.fprintf fmt "@,";
|
|
pp fmt c)
|
|
children
|
|
in
|
|
pp fmt (t ());
|
|
()
|
|
|
|
(** {2 Pretty printing in the DOT (graphviz) format} *)
|
|
|
|
module Dot = struct
|
|
type attribute =
|
|
[ `Color of string
|
|
| `Shape of string
|
|
| `Weight of int
|
|
| `Style of string
|
|
| `Label of string
|
|
| `Id of string
|
|
| `Other of string * string ]
|
|
(** Dot attributes for nodes *)
|
|
|
|
type graph = string * attribute list t list
|
|
(** A dot graph is a name, plus a list of trees labelled with attributes *)
|
|
|
|
let mk_id format =
|
|
let buf = Buffer.create 64 in
|
|
Printf.kbprintf (fun _ -> `Id (Buffer.contents buf)) buf format
|
|
|
|
let mk_label format =
|
|
let buf = Buffer.create 64 in
|
|
Printf.kbprintf (fun _ -> `Label (Buffer.contents buf)) buf format
|
|
|
|
let make ~name l = name, l
|
|
let singleton ~name t = name, [ t ]
|
|
|
|
(* find and remove the `Id attribute, if any *)
|
|
let rec _find_id acc l =
|
|
match l with
|
|
| [] -> raise Not_found
|
|
| `Id n :: l' -> n, List.rev_append acc l'
|
|
| x :: l' -> _find_id (x :: acc) l'
|
|
|
|
let _pp_attr fmt attr =
|
|
match attr with
|
|
| `Color c -> Format.fprintf fmt "color=%s" c
|
|
| `Shape s -> Format.fprintf fmt "shape=%s" s
|
|
| `Weight w -> Format.fprintf fmt "weight=%d" w
|
|
| `Style s -> Format.fprintf fmt "style=%s" s
|
|
| `Label l -> Format.fprintf fmt "label=\"%s\"" l
|
|
| `Other (name, value) -> Format.fprintf fmt "%s=\"%s\"" name value
|
|
| `Id _ -> ()
|
|
(* should not be here *)
|
|
|
|
let rec _pp_attrs fmt l =
|
|
match l with
|
|
| [] -> ()
|
|
| [ x ] -> _pp_attr fmt x
|
|
| x :: l' ->
|
|
_pp_attr fmt x;
|
|
Format.pp_print_char fmt ',';
|
|
_pp_attrs fmt l'
|
|
|
|
let pp out (name, l) =
|
|
(* nodes already printed *)
|
|
let tbl = Hashtbl.create 32 in
|
|
(* fresh name generator *)
|
|
let new_name =
|
|
let n = ref 0 in
|
|
fun () ->
|
|
let s = Printf.sprintf "node_%d" !n in
|
|
incr n;
|
|
s
|
|
in
|
|
(* the name for some node is either defined, either a fresh random
|
|
name *)
|
|
let get_name x = try _find_id [] x with Not_found -> new_name (), x in
|
|
(* recursive printing (bfs) *)
|
|
let rec aux q =
|
|
if FQ.is_empty q then
|
|
()
|
|
else (
|
|
let (parent, x), q' = FQ.pop_exn q in
|
|
let q' = pp_node q' ?parent x in
|
|
aux q'
|
|
)
|
|
and pp_node q ?parent t =
|
|
match t () with
|
|
| `Nil -> q
|
|
| `Node (x, l) ->
|
|
let name, attrs = get_name x in
|
|
(match parent with
|
|
| None -> ()
|
|
| Some n -> Format.fprintf out " %s -> %s;@," n name);
|
|
if not (Hashtbl.mem tbl name) then (
|
|
Hashtbl.add tbl name ();
|
|
Format.fprintf out "@[%s [%a];@]@," name _pp_attrs attrs;
|
|
List.fold_left (fun q y -> FQ.push q (Some name, y)) q l
|
|
) else
|
|
q
|
|
in
|
|
let q = List.fold_left (fun q y -> FQ.push q (None, y)) FQ.empty l in
|
|
(* preamble *)
|
|
Format.fprintf out "@[<hv 2>digraph \"%s\" {@," name;
|
|
aux q;
|
|
Format.fprintf out "}@]@.";
|
|
()
|
|
|
|
let pp_single name out t = pp out (singleton ~name t)
|
|
|
|
let print_to_file filename g =
|
|
let oc = open_out filename in
|
|
let fmt = Format.formatter_of_out_channel oc in
|
|
try
|
|
pp fmt g;
|
|
Format.pp_print_flush fmt ();
|
|
close_out oc
|
|
with e ->
|
|
close_out oc;
|
|
raise e
|
|
|
|
let to_file ?(name = "graph") filename trees =
|
|
let g = make ~name trees in
|
|
print_to_file filename g
|
|
end
|