File: dag.ml

package info (click to toggle)
ocaml-obuild 0.1.11-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 796 kB
  • sloc: ml: 6,570; sh: 171; ansic: 34; makefile: 11
file content (227 lines) | stat: -rw-r--r-- 7,560 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
(* simple bi-directional DAG implementation using shallow link*)
open Printf
open Ext.Compat
    
(* represent a node that point shallowly to children and parents *)
type 'a dagnode =
    { mutable parents  : 'a list
    ; mutable children : 'a list
    }

(* TODO add a 'a <-> int table, so that indexing can be done on int instead and
   that lists can be replaced by set *)
type 'a t =
    { nodes : ('a, 'a dagnode) Hashtbl.t
    }

let init () = { nodes = Hashtbl.create 16 }

let length dag = Hashtbl.length dag.nodes

(* Add an directed edge from a to b.
 *
 * 'a' is the parent of 'b'
 * 'b' is the child of 'a'
 *)
let addEdge a b dag =
    let maNode = try Some (Hashtbl.find dag.nodes a) with Not_found -> None in
    let mbNode = try Some (Hashtbl.find dag.nodes b) with Not_found -> None in
    (match (maNode, mbNode) with
    | None, None       ->
        Hashtbl.add dag.nodes a { parents = []; children = [b] };
        Hashtbl.add dag.nodes b { parents = [a]; children = [] }
    | Some aNode, None ->
        if not (List.mem b aNode.children) then aNode.children <- b :: aNode.children;
        Hashtbl.add dag.nodes b { parents = [a]; children = [] }
    | None, Some bNode ->
        if not (List.mem a bNode.children) then bNode.parents <- a :: bNode.parents;
        Hashtbl.add dag.nodes a { parents = []; children = [b] }
    | Some aNode, Some bNode ->
        if not (List.mem b aNode.children) then aNode.children <- b :: aNode.children;
        if not (List.mem a bNode.children) then bNode.parents <- a :: bNode.parents
    );
    ()

exception DagNode_Not_found
exception DagNode_Already_Exists

let addNode a dag =
    try let _ = Hashtbl.find dag.nodes a in ()
    with Not_found -> Hashtbl.add dag.nodes a { parents = []; children = [] }

let addNode_exclusive a dag =
    try let _ = Hashtbl.find dag.nodes a in raise DagNode_Already_Exists
    with Not_found -> Hashtbl.add dag.nodes a { parents = []; children = [] }

(* has edge from a to b *)
let hasEdge a b dag =
    let maNode = try Some (Hashtbl.find dag.nodes a) with Not_found -> None in
    let mbNode = try Some (Hashtbl.find dag.nodes b) with Not_found -> None in
    match (maNode, mbNode) with
    | Some aNode, Some bNode -> List.mem b aNode.children && List.mem a bNode.parents
    | _                      -> false

let delEdge a b dag =
    let maNode = try Some (Hashtbl.find dag.nodes a) with Not_found -> None in
    let mbNode = try Some (Hashtbl.find dag.nodes b) with Not_found -> None in
    (match (maNode, mbNode) with
    | Some aNode, Some bNode ->
        aNode.children <- List.filter (fun x -> x <> b) aNode.children;
        bNode.parents  <- List.filter (fun x -> x <> a) bNode.parents
    | _ -> ()
    )

let addEdges l dag =
    List.iter (fun (n1, n2) -> addEdge n1 n2 dag) l

(*  add edges connected to each other in a list
 *  n1 -> n2 -> n3 -> ... -> nn
 *)
let addEdgesConnected l dag =
    let rec loop parent nodes =
        match nodes with
        | []    -> ()
        | n::ns -> addEdge parent n dag; loop n ns
        in
    match l with
    | []    -> ()
    | x::[] -> addNode x dag
    | x::l  -> loop x l


(*  add children edges with p the parent
 *  p -> l[1], p -> l[2], ..., p -> l[n]
 *)
let addChildrenEdges p l dag =
    List.iter (fun x -> addEdge p x dag) l

let existsNode a dag = Hashtbl.mem dag.nodes a

let getLeaves dag =
    Hashtbl.fold (fun k v acc -> if v.children = [] then k::acc else acc) dag.nodes []

let getRoots dag =
    Hashtbl.fold (fun k v acc -> if v.parents = [] then k::acc else acc) dag.nodes []

let getNode dag a = try Hashtbl.find dag.nodes a
                    with Not_found -> raise DagNode_Not_found
let getNodes dag = Hashtbl.fold (fun k _ acc -> k :: acc) dag.nodes []

let getChildren dag a = (getNode dag a).children

let getParents dag a = (getNode dag a).parents

let rec getChildren_full dag a = 
    let children = getChildren dag a in
    children @ List.concat (List.map (getChildren_full dag) children)

let isChildren dag a b = List.mem b (getChildren dag a)

let rec isChildren_full dag a b =
    let children = getChildren dag a in
    (* either it's present here, or in one of the kiddy *)
    List.mem b children ||
    List.fold_left (fun acc child ->
        acc || isChildren_full dag child b
    ) false children

let subset dag roots =
    let subdag = init () in
    let rec loop node =
        addNode node subdag;
        let children = getChildren dag node in
        List.iter (fun child -> addEdge node child subdag; loop child) children
        in
    List.iter (fun root -> loop root) roots;
    subdag

let copy dag =
    let nodes = Hashtbl.fold (fun k _ acc -> k :: acc) dag.nodes [] in
    let dag2 = init () in
    let copy_node node =
        addNode node dag2;
        let children = getChildren dag node in
        addChildrenEdges node children dag2
        in
    List.iter (fun node -> copy_node node) nodes;
    dag2

let merge dest src =
  let nodes = Hashtbl.fold (fun k _ acc -> k :: acc) src.nodes [] in
  let dups = ref [] in
  List.iter (fun node -> if existsNode node dest then dups := node :: !dups) nodes;
  let copy_node node =
    addNode node dest;
    let children = getChildren src node in
    addChildrenEdges node children dest
  in
  List.iter (fun node -> copy_node node) nodes;
  !dups

(* o(v^3) use with care *)
let transitive_reduction dag =
    let reducedDag = copy dag in
    (* this is sub optimal, as we re-lookup nodes everytimes in hasEdge AND delEdge.
     * would go away automatically when having the lookup dict with sets. *)
    let nodes = Hashtbl.fold (fun k _ acc -> k :: acc) dag.nodes [] in
    List.iter (fun x ->
        List.iter (fun y ->
            List.iter (fun z ->
                if hasEdge x y dag && hasEdge y z dag
                    then delEdge x z reducedDag
                    else ()
            ) nodes
        ) nodes
    ) nodes;
    reducedDag

(* this is for debugging the DAG.
 * dump the dag links and node in a textual format *)
let dump a_to_string dag =
    let all = getNodes dag in
    List.iter (fun n ->
        printf "%s:\n" (a_to_string n);
        printf "  | parents  = %s\n" (String.concat ", " (List.map a_to_string (getParents dag n)));
        printf "  | children = %s\n" (String.concat ", " (List.map a_to_string (getChildren dag n)))
    ) all

(* it's useful to be able to visualize the DAG with the excellent dot
 *)
let toDot a_to_string name fromLeaf dag =
    let buf = Buffer.create 1024 in
    let nodes = getNodes dag in
    let dotIndex = Hashtbl.create (List.length nodes) in
    let append = Buffer.add_string buf in
    let sanitizeName = bytes_of_string name in
    for i = 0 to String.length name - 1
    do
      if (bytes_get sanitizeName i) = '-'
      then bytes_set sanitizeName i '_'
    done;

    append ("digraph " ^ (bytes_to_string sanitizeName) ^ " {\n");

    let list_iteri f list =
        let rec loop i l =
            match l with
            | []    -> ()
            | x::xs -> f i x; loop (i+1) xs
            in
        loop 1 list
        in

    list_iteri (fun i n ->
        Hashtbl.add dotIndex n i;
        append (sprintf "  %d [label = \"%s\"];\n" i (a_to_string n));
    ) nodes;

    List.iter (fun n ->
        let i = Hashtbl.find dotIndex n in
        List.iter (fun child ->
            let ci = Hashtbl.find dotIndex child in
            append (sprintf "  %d -> %d;\n" i ci)
        ) ((if fromLeaf then getParents else getChildren) dag n)
    ) nodes;
    
    append "}\n";
    Buffer.contents buf