UNPKG

14.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Graph = void 0;
4/* eslint-disable prefer-rest-params */
5/* eslint-disable @typescript-eslint/no-this-alias */
6const constant = require("lodash.constant");
7const each = require("lodash.foreach");
8const _filter = require("lodash.foreach");
9const isEmpty = require("lodash.isempty");
10const isFunction = require("lodash.isfunction");
11const isUndefined = require("lodash.isundefined");
12const keys = require("lodash.keys");
13const reduce = require("lodash.reduce");
14const union = require("lodash.union");
15const values = require("lodash.values");
16const DEFAULT_EDGE_NAME = '\x00';
17const GRAPH_NODE = '\x00';
18const EDGE_KEY_DELIM = '\x01';
19// Implementation notes:
20//
21// * Node id query functions should return string ids for the nodes
22// * Edge id query functions should return an "edgeObj", edge object, that is
23// composed of enough information to uniquely identify an edge: {v, w, name}.
24// * Internally we use an "edgeId", a stringified form of the edgeObj, to
25// reference edges. This is because we need a performant way to look these
26// edges up and, object properties, which have string keys, are the closest
27// we're going to get to a performant hashtable in JavaScript.
28class Graph {
29 constructor(opts) {
30 var _a, _b, _c;
31 /* Number of nodes in the graph. Should only be changed by the implementation. */
32 this._nodeCount = 0;
33 /* Number of edges in the graph. Should only be changed by the implementation. */
34 this._edgeCount = 0;
35 this._isDirected = (_a = opts === null || opts === void 0 ? void 0 : opts.directed) !== null && _a !== void 0 ? _a : true;
36 this._isMultigraph = (_b = opts === null || opts === void 0 ? void 0 : opts.multigraph) !== null && _b !== void 0 ? _b : false;
37 this._isCompound = (_c = opts === null || opts === void 0 ? void 0 : opts.compound) !== null && _c !== void 0 ? _c : false;
38 // Label for the graph itself
39 this._label = undefined;
40 // Defaults to be set when creating a new node
41 this._defaultNodeLabelFn = constant(undefined);
42 // Defaults to be set when creating a new edge
43 this._defaultEdgeLabelFn = constant(undefined);
44 // v -> label
45 this._nodes = {};
46 if (this._isCompound) {
47 // v -> parent
48 this._parent = {};
49 // v -> children
50 this._children = {};
51 this._children[GRAPH_NODE] = {};
52 }
53 // v -> edgeObj
54 this._in = {};
55 // u -> v -> Number
56 this._preds = {};
57 // v -> edgeObj
58 this._out = {};
59 // v -> w -> Number
60 this._sucs = {};
61 // e -> edgeObj
62 this._edgeObjs = {};
63 // e -> label
64 this._edgeLabels = {};
65 }
66 /* === Graph functions ========= */
67 isDirected() {
68 return this._isDirected;
69 }
70 isMultigraph() {
71 return this._isMultigraph;
72 }
73 isCompound() {
74 return this._isCompound;
75 }
76 setGraph(label) {
77 this._label = label;
78 return this;
79 }
80 graph() {
81 return this._label;
82 }
83 /* === Node functions ========== */
84 setDefaultNodeLabel(newDefault) {
85 if (!isFunction(newDefault)) {
86 newDefault = constant(newDefault);
87 }
88 this._defaultNodeLabelFn = newDefault;
89 return this;
90 }
91 nodeCount() {
92 return this._nodeCount;
93 }
94 nodes() {
95 return keys(this._nodes);
96 }
97 sources() {
98 const self = this;
99 return _filter(this.nodes(), function (v) {
100 return isEmpty(self._in[v]);
101 });
102 }
103 sinks() {
104 const self = this;
105 return _filter(this.nodes(), function (v) {
106 return isEmpty(self._out[v]);
107 });
108 }
109 setNodes(vs, value) {
110 const args = arguments;
111 const self = this;
112 each(vs, function (v) {
113 if (args.length > 1) {
114 self.setNode(v, value);
115 }
116 else {
117 self.setNode(v);
118 }
119 });
120 return this;
121 }
122 setNode(v, value) {
123 if (v in this._nodes) {
124 if (arguments.length > 1) {
125 this._nodes[v] = value;
126 }
127 return this;
128 }
129 this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
130 if (this._isCompound) {
131 this._parent[v] = GRAPH_NODE;
132 this._children[v] = {};
133 this._children[GRAPH_NODE][v] = true;
134 }
135 this._in[v] = {};
136 this._preds[v] = {};
137 this._out[v] = {};
138 this._sucs[v] = {};
139 ++this._nodeCount;
140 return this;
141 }
142 node(v) {
143 return this._nodes[v];
144 }
145 hasNode(v) {
146 return v in this._nodes;
147 }
148 removeNode(v) {
149 const self = this;
150 if (v in this._nodes) {
151 const removeEdge = function (e) {
152 self.removeEdge(self._edgeObjs[e]);
153 };
154 delete this._nodes[v];
155 if (this._isCompound) {
156 this._removeFromParentsChildList(v);
157 delete this._parent[v];
158 each(this.children(v), function (child) {
159 self.setParent(child);
160 });
161 delete this._children[v];
162 }
163 each(keys(this._in[v]), removeEdge);
164 delete this._in[v];
165 delete this._preds[v];
166 each(keys(this._out[v]), removeEdge);
167 delete this._out[v];
168 delete this._sucs[v];
169 --this._nodeCount;
170 }
171 return this;
172 }
173 setParent(v, parent) {
174 if (!this._isCompound) {
175 throw new Error('Cannot set parent in a non-compound graph');
176 }
177 if (isUndefined(parent)) {
178 parent = GRAPH_NODE;
179 }
180 else {
181 // Coerce parent to string
182 parent += '';
183 for (let ancestor = parent; !isUndefined(ancestor); ancestor = this.parent(ancestor)) {
184 if (ancestor === v) {
185 throw new Error('Setting ' +
186 parent +
187 ' as parent of ' +
188 v +
189 ' would create a cycle');
190 }
191 }
192 this.setNode(parent);
193 }
194 this.setNode(v);
195 this._removeFromParentsChildList(v);
196 this._parent[v] = parent;
197 this._children[parent][v] = true;
198 return this;
199 }
200 _removeFromParentsChildList(v) {
201 delete this._children[this._parent[v]][v];
202 }
203 parent(v) {
204 if (this._isCompound) {
205 const parent = this._parent[v];
206 if (parent !== GRAPH_NODE) {
207 return parent;
208 }
209 }
210 }
211 children(v) {
212 if (isUndefined(v)) {
213 v = GRAPH_NODE;
214 }
215 if (this._isCompound) {
216 const children = this._children[v];
217 if (children) {
218 return keys(children);
219 }
220 }
221 else if (v === GRAPH_NODE) {
222 return this.nodes();
223 }
224 else if (this.hasNode(v)) {
225 return [];
226 }
227 }
228 predecessors(v) {
229 const predsV = this._preds[v];
230 if (predsV) {
231 return keys(predsV);
232 }
233 }
234 successors(v) {
235 const sucsV = this._sucs[v];
236 if (sucsV) {
237 return keys(sucsV);
238 }
239 }
240 neighbors(v) {
241 const preds = this.predecessors(v);
242 if (preds) {
243 return union(preds, this.successors(v));
244 }
245 }
246 isLeaf(v) {
247 let neighbors;
248 if (this.isDirected()) {
249 neighbors = this.successors(v);
250 }
251 else {
252 neighbors = this.neighbors(v);
253 }
254 return neighbors.length === 0;
255 }
256 filterNodes(filter) {
257 const copy = new Graph({
258 directed: this._isDirected,
259 multigraph: this._isMultigraph,
260 compound: this._isCompound,
261 });
262 copy.setGraph(this.graph());
263 const self = this;
264 each(this._nodes, function (value, v) {
265 if (filter(v)) {
266 copy.setNode(v, value);
267 }
268 });
269 each(this._edgeObjs, function (e) {
270 if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
271 copy.setEdge(e, self.edge(e));
272 }
273 });
274 const parents = {};
275 function findParent(v) {
276 const parent = self.parent(v);
277 if (parent === undefined || copy.hasNode(parent)) {
278 parents[v] = parent;
279 return parent;
280 }
281 else if (parent in parents) {
282 return parents[parent];
283 }
284 else {
285 return findParent(parent);
286 }
287 }
288 if (this._isCompound) {
289 each(copy.nodes(), function (v) {
290 copy.setParent(v, findParent(v));
291 });
292 }
293 return copy;
294 }
295 /* === Edge functions ========== */
296 setDefaultEdgeLabel(newDefault) {
297 if (!isFunction(newDefault)) {
298 newDefault = constant(newDefault);
299 }
300 this._defaultEdgeLabelFn = newDefault;
301 return this;
302 }
303 edgeCount() {
304 return this._edgeCount;
305 }
306 edges() {
307 return values(this._edgeObjs);
308 }
309 setPath(vs, value) {
310 const self = this;
311 const args = arguments;
312 reduce(vs, function (v, w) {
313 if (args.length > 1) {
314 self.setEdge(v, w, value);
315 }
316 else {
317 self.setEdge(v, w);
318 }
319 return w;
320 });
321 return this;
322 }
323 // eslint-disable-next-line @typescript-eslint/no-unused-vars
324 setEdge(...args) {
325 let v, w, name, value;
326 let valueSpecified = false;
327 const arg0 = arguments[0];
328 if (typeof arg0 === 'object' && arg0 !== null && 'v' in arg0) {
329 v = arg0.v;
330 w = arg0.w;
331 name = arg0.name;
332 if (arguments.length === 2) {
333 value = arguments[1];
334 valueSpecified = true;
335 }
336 }
337 else {
338 v = arg0;
339 w = arguments[1];
340 name = arguments[3];
341 if (arguments.length > 2) {
342 value = arguments[2];
343 valueSpecified = true;
344 }
345 }
346 v = '' + v;
347 w = '' + w;
348 if (!isUndefined(name)) {
349 name = '' + name;
350 }
351 const e = edgeArgsToId(this._isDirected, v, w, name);
352 if (e in this._edgeLabels) {
353 if (valueSpecified) {
354 this._edgeLabels[e] = value;
355 }
356 return this;
357 }
358 if (!isUndefined(name) && !this._isMultigraph) {
359 throw new Error('Cannot set a named edge when isMultigraph = false');
360 }
361 // It didn't exist, so we need to create it.
362 // First ensure the nodes exist.
363 this.setNode(v);
364 this.setNode(w);
365 this._edgeLabels[e] = valueSpecified
366 ? value
367 : this._defaultEdgeLabelFn(v, w, name);
368 const edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
369 // Ensure we add undirected edges in a consistent way.
370 v = edgeObj.v;
371 w = edgeObj.w;
372 Object.freeze(edgeObj);
373 this._edgeObjs[e] = edgeObj;
374 incrementOrInitEntry(this._preds[w], v);
375 incrementOrInitEntry(this._sucs[v], w);
376 this._in[w][e] = edgeObj;
377 this._out[v][e] = edgeObj;
378 this._edgeCount++;
379 return this;
380 }
381 edge(v, w, name) {
382 const e = arguments.length === 1
383 ? edgeObjToId(this._isDirected, arguments[0])
384 : edgeArgsToId(this._isDirected, v, w, name);
385 return this._edgeLabels[e];
386 }
387 hasEdge(v, w, name) {
388 const e = arguments.length === 1
389 ? edgeObjToId(this._isDirected, arguments[0])
390 : edgeArgsToId(this._isDirected, v, w, name);
391 return e in this._edgeLabels;
392 }
393 removeEdge(v, w, name) {
394 const e = arguments.length === 1
395 ? edgeObjToId(this._isDirected, arguments[0])
396 : edgeArgsToId(this._isDirected, v, w, name);
397 const edge = this._edgeObjs[e];
398 if (edge) {
399 v = edge.v;
400 w = edge.w;
401 delete this._edgeLabels[e];
402 delete this._edgeObjs[e];
403 decrementOrRemoveEntry(this._preds[w], v);
404 decrementOrRemoveEntry(this._sucs[v], w);
405 delete this._in[w][e];
406 delete this._out[v][e];
407 this._edgeCount--;
408 }
409 return this;
410 }
411 inEdges(v, u) {
412 const inV = this._in[v];
413 if (inV) {
414 const edges = values(inV);
415 if (!u) {
416 return edges;
417 }
418 return _filter(edges, function (edge) {
419 return edge.v === u;
420 });
421 }
422 }
423 outEdges(v, w) {
424 const outV = this._out[v];
425 if (outV) {
426 const edges = values(outV);
427 if (!w) {
428 return edges;
429 }
430 return _filter(edges, function (edge) {
431 return edge.w === w;
432 });
433 }
434 }
435 nodeEdges(v, w) {
436 const inEdges = this.inEdges(v, w);
437 if (inEdges) {
438 return inEdges.concat(this.outEdges(v, w));
439 }
440 }
441}
442exports.Graph = Graph;
443function incrementOrInitEntry(map, k) {
444 if (map[k]) {
445 map[k]++;
446 }
447 else {
448 map[k] = 1;
449 }
450}
451function decrementOrRemoveEntry(map, k) {
452 if (!--map[k]) {
453 delete map[k];
454 }
455}
456function edgeArgsToId(isDirected, v_, w_, name) {
457 let v = '' + v_;
458 let w = '' + w_;
459 if (!isDirected && v > w) {
460 const tmp = v;
461 v = w;
462 w = tmp;
463 }
464 return (v +
465 EDGE_KEY_DELIM +
466 w +
467 EDGE_KEY_DELIM +
468 (isUndefined(name) ? DEFAULT_EDGE_NAME : name));
469}
470function edgeArgsToObj(isDirected, v_, w_, name) {
471 let v = '' + v_;
472 let w = '' + w_;
473 if (!isDirected && v > w) {
474 const tmp = v;
475 v = w;
476 w = tmp;
477 }
478 const edgeObj = { v: v, w: w };
479 if (name) {
480 edgeObj.name = name;
481 }
482 return edgeObj;
483}
484function edgeObjToId(isDirected, edgeObj) {
485 return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
486}
487//# sourceMappingURL=graph.js.map
\No newline at end of file