Spaces:
Build error
Build error
| import * as _ from 'lodash-es'; | |
| var DEFAULT_EDGE_NAME = '\x00'; | |
| var GRAPH_NODE = '\x00'; | |
| var EDGE_KEY_DELIM = '\x01'; | |
| // Implementation notes: | |
| // | |
| // * Node id query functions should return string ids for the nodes | |
| // * Edge id query functions should return an "edgeObj", edge object, that is | |
| // composed of enough information to uniquely identify an edge: {v, w, name}. | |
| // * Internally we use an "edgeId", a stringified form of the edgeObj, to | |
| // reference edges. This is because we need a performant way to look these | |
| // edges up and, object properties, which have string keys, are the closest | |
| // we're going to get to a performant hashtable in JavaScript. | |
| // Implementation notes: | |
| // | |
| // * Node id query functions should return string ids for the nodes | |
| // * Edge id query functions should return an "edgeObj", edge object, that is | |
| // composed of enough information to uniquely identify an edge: {v, w, name}. | |
| // * Internally we use an "edgeId", a stringified form of the edgeObj, to | |
| // reference edges. This is because we need a performant way to look these | |
| // edges up and, object properties, which have string keys, are the closest | |
| // we're going to get to a performant hashtable in JavaScript. | |
| export class Graph { | |
| constructor(opts = {}) { | |
| this._isDirected = _.has(opts, 'directed') ? opts.directed : true; | |
| this._isMultigraph = _.has(opts, 'multigraph') ? opts.multigraph : false; | |
| this._isCompound = _.has(opts, 'compound') ? opts.compound : false; | |
| // Label for the graph itself | |
| this._label = undefined; | |
| // Defaults to be set when creating a new node | |
| this._defaultNodeLabelFn = _.constant(undefined); | |
| // Defaults to be set when creating a new edge | |
| this._defaultEdgeLabelFn = _.constant(undefined); | |
| // v -> label | |
| this._nodes = {}; | |
| if (this._isCompound) { | |
| // v -> parent | |
| this._parent = {}; | |
| // v -> children | |
| this._children = {}; | |
| this._children[GRAPH_NODE] = {}; | |
| } | |
| // v -> edgeObj | |
| this._in = {}; | |
| // u -> v -> Number | |
| this._preds = {}; | |
| // v -> edgeObj | |
| this._out = {}; | |
| // v -> w -> Number | |
| this._sucs = {}; | |
| // e -> edgeObj | |
| this._edgeObjs = {}; | |
| // e -> label | |
| this._edgeLabels = {}; | |
| } | |
| /* === Graph functions ========= */ | |
| isDirected() { | |
| return this._isDirected; | |
| } | |
| isMultigraph() { | |
| return this._isMultigraph; | |
| } | |
| isCompound() { | |
| return this._isCompound; | |
| } | |
| setGraph(label) { | |
| this._label = label; | |
| return this; | |
| } | |
| graph() { | |
| return this._label; | |
| } | |
| /* === Node functions ========== */ | |
| setDefaultNodeLabel(newDefault) { | |
| if (!_.isFunction(newDefault)) { | |
| newDefault = _.constant(newDefault); | |
| } | |
| this._defaultNodeLabelFn = newDefault; | |
| return this; | |
| } | |
| nodeCount() { | |
| return this._nodeCount; | |
| } | |
| nodes() { | |
| return _.keys(this._nodes); | |
| } | |
| sources() { | |
| var self = this; | |
| return _.filter(this.nodes(), function (v) { | |
| return _.isEmpty(self._in[v]); | |
| }); | |
| } | |
| sinks() { | |
| var self = this; | |
| return _.filter(this.nodes(), function (v) { | |
| return _.isEmpty(self._out[v]); | |
| }); | |
| } | |
| setNodes(vs, value) { | |
| var args = arguments; | |
| var self = this; | |
| _.each(vs, function (v) { | |
| if (args.length > 1) { | |
| self.setNode(v, value); | |
| } else { | |
| self.setNode(v); | |
| } | |
| }); | |
| return this; | |
| } | |
| setNode(v, value) { | |
| if (_.has(this._nodes, v)) { | |
| if (arguments.length > 1) { | |
| this._nodes[v] = value; | |
| } | |
| return this; | |
| } | |
| // @ts-expect-error | |
| this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v); | |
| if (this._isCompound) { | |
| this._parent[v] = GRAPH_NODE; | |
| this._children[v] = {}; | |
| this._children[GRAPH_NODE][v] = true; | |
| } | |
| this._in[v] = {}; | |
| this._preds[v] = {}; | |
| this._out[v] = {}; | |
| this._sucs[v] = {}; | |
| ++this._nodeCount; | |
| return this; | |
| } | |
| node(v) { | |
| return this._nodes[v]; | |
| } | |
| hasNode(v) { | |
| return _.has(this._nodes, v); | |
| } | |
| removeNode(v) { | |
| var self = this; | |
| if (_.has(this._nodes, v)) { | |
| var removeEdge = function (e) { | |
| self.removeEdge(self._edgeObjs[e]); | |
| }; | |
| delete this._nodes[v]; | |
| if (this._isCompound) { | |
| this._removeFromParentsChildList(v); | |
| delete this._parent[v]; | |
| _.each(this.children(v), function (child) { | |
| self.setParent(child); | |
| }); | |
| delete this._children[v]; | |
| } | |
| _.each(_.keys(this._in[v]), removeEdge); | |
| delete this._in[v]; | |
| delete this._preds[v]; | |
| _.each(_.keys(this._out[v]), removeEdge); | |
| delete this._out[v]; | |
| delete this._sucs[v]; | |
| --this._nodeCount; | |
| } | |
| return this; | |
| } | |
| setParent(v, parent) { | |
| if (!this._isCompound) { | |
| throw new Error('Cannot set parent in a non-compound graph'); | |
| } | |
| if (_.isUndefined(parent)) { | |
| parent = GRAPH_NODE; | |
| } else { | |
| // Coerce parent to string | |
| parent += ''; | |
| for (var ancestor = parent; !_.isUndefined(ancestor); ancestor = this.parent(ancestor)) { | |
| if (ancestor === v) { | |
| throw new Error('Setting ' + parent + ' as parent of ' + v + ' would create a cycle'); | |
| } | |
| } | |
| this.setNode(parent); | |
| } | |
| this.setNode(v); | |
| this._removeFromParentsChildList(v); | |
| this._parent[v] = parent; | |
| this._children[parent][v] = true; | |
| return this; | |
| } | |
| _removeFromParentsChildList(v) { | |
| delete this._children[this._parent[v]][v]; | |
| } | |
| parent(v) { | |
| if (this._isCompound) { | |
| var parent = this._parent[v]; | |
| if (parent !== GRAPH_NODE) { | |
| return parent; | |
| } | |
| } | |
| } | |
| children(v) { | |
| if (_.isUndefined(v)) { | |
| v = GRAPH_NODE; | |
| } | |
| if (this._isCompound) { | |
| var children = this._children[v]; | |
| if (children) { | |
| return _.keys(children); | |
| } | |
| } else if (v === GRAPH_NODE) { | |
| return this.nodes(); | |
| } else if (this.hasNode(v)) { | |
| return []; | |
| } | |
| } | |
| predecessors(v) { | |
| var predsV = this._preds[v]; | |
| if (predsV) { | |
| return _.keys(predsV); | |
| } | |
| } | |
| successors(v) { | |
| var sucsV = this._sucs[v]; | |
| if (sucsV) { | |
| return _.keys(sucsV); | |
| } | |
| } | |
| neighbors(v) { | |
| var preds = this.predecessors(v); | |
| if (preds) { | |
| return _.union(preds, this.successors(v)); | |
| } | |
| } | |
| isLeaf(v) { | |
| var neighbors; | |
| if (this.isDirected()) { | |
| neighbors = this.successors(v); | |
| } else { | |
| neighbors = this.neighbors(v); | |
| } | |
| return neighbors.length === 0; | |
| } | |
| filterNodes(filter) { | |
| // @ts-expect-error | |
| var copy = new this.constructor({ | |
| directed: this._isDirected, | |
| multigraph: this._isMultigraph, | |
| compound: this._isCompound, | |
| }); | |
| copy.setGraph(this.graph()); | |
| var self = this; | |
| _.each(this._nodes, function (value, v) { | |
| if (filter(v)) { | |
| copy.setNode(v, value); | |
| } | |
| }); | |
| _.each(this._edgeObjs, function (e) { | |
| // @ts-expect-error | |
| if (copy.hasNode(e.v) && copy.hasNode(e.w)) { | |
| copy.setEdge(e, self.edge(e)); | |
| } | |
| }); | |
| var parents = {}; | |
| function findParent(v) { | |
| var parent = self.parent(v); | |
| if (parent === undefined || copy.hasNode(parent)) { | |
| parents[v] = parent; | |
| return parent; | |
| } else if (parent in parents) { | |
| return parents[parent]; | |
| } else { | |
| return findParent(parent); | |
| } | |
| } | |
| if (this._isCompound) { | |
| _.each(copy.nodes(), function (v) { | |
| copy.setParent(v, findParent(v)); | |
| }); | |
| } | |
| return copy; | |
| } | |
| /* === Edge functions ========== */ | |
| setDefaultEdgeLabel(newDefault) { | |
| if (!_.isFunction(newDefault)) { | |
| newDefault = _.constant(newDefault); | |
| } | |
| this._defaultEdgeLabelFn = newDefault; | |
| return this; | |
| } | |
| edgeCount() { | |
| return this._edgeCount; | |
| } | |
| edges() { | |
| return _.values(this._edgeObjs); | |
| } | |
| setPath(vs, value) { | |
| var self = this; | |
| var args = arguments; | |
| _.reduce(vs, function (v, w) { | |
| if (args.length > 1) { | |
| self.setEdge(v, w, value); | |
| } else { | |
| self.setEdge(v, w); | |
| } | |
| return w; | |
| }); | |
| return this; | |
| } | |
| /* | |
| * setEdge(v, w, [value, [name]]) | |
| * setEdge({ v, w, [name] }, [value]) | |
| */ | |
| setEdge() { | |
| var v, w, name, value; | |
| var valueSpecified = false; | |
| var arg0 = arguments[0]; | |
| if (typeof arg0 === 'object' && arg0 !== null && 'v' in arg0) { | |
| v = arg0.v; | |
| w = arg0.w; | |
| name = arg0.name; | |
| if (arguments.length === 2) { | |
| value = arguments[1]; | |
| valueSpecified = true; | |
| } | |
| } else { | |
| v = arg0; | |
| w = arguments[1]; | |
| name = arguments[3]; | |
| if (arguments.length > 2) { | |
| value = arguments[2]; | |
| valueSpecified = true; | |
| } | |
| } | |
| v = '' + v; | |
| w = '' + w; | |
| if (!_.isUndefined(name)) { | |
| name = '' + name; | |
| } | |
| var e = edgeArgsToId(this._isDirected, v, w, name); | |
| if (_.has(this._edgeLabels, e)) { | |
| if (valueSpecified) { | |
| this._edgeLabels[e] = value; | |
| } | |
| return this; | |
| } | |
| if (!_.isUndefined(name) && !this._isMultigraph) { | |
| throw new Error('Cannot set a named edge when isMultigraph = false'); | |
| } | |
| // It didn't exist, so we need to create it. | |
| // First ensure the nodes exist. | |
| this.setNode(v); | |
| this.setNode(w); | |
| // @ts-expect-error | |
| this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name); | |
| var edgeObj = edgeArgsToObj(this._isDirected, v, w, name); | |
| // Ensure we add undirected edges in a consistent way. | |
| v = edgeObj.v; | |
| w = edgeObj.w; | |
| Object.freeze(edgeObj); | |
| this._edgeObjs[e] = edgeObj; | |
| incrementOrInitEntry(this._preds[w], v); | |
| incrementOrInitEntry(this._sucs[v], w); | |
| this._in[w][e] = edgeObj; | |
| this._out[v][e] = edgeObj; | |
| this._edgeCount++; | |
| return this; | |
| } | |
| edge(v, w, name) { | |
| var e = | |
| arguments.length === 1 | |
| ? edgeObjToId(this._isDirected, arguments[0]) | |
| : edgeArgsToId(this._isDirected, v, w, name); | |
| return this._edgeLabels[e]; | |
| } | |
| hasEdge(v, w, name) { | |
| var e = | |
| arguments.length === 1 | |
| ? edgeObjToId(this._isDirected, arguments[0]) | |
| : edgeArgsToId(this._isDirected, v, w, name); | |
| return _.has(this._edgeLabels, e); | |
| } | |
| removeEdge(v, w, name) { | |
| var e = | |
| arguments.length === 1 | |
| ? edgeObjToId(this._isDirected, arguments[0]) | |
| : edgeArgsToId(this._isDirected, v, w, name); | |
| var edge = this._edgeObjs[e]; | |
| if (edge) { | |
| v = edge.v; | |
| w = edge.w; | |
| delete this._edgeLabels[e]; | |
| delete this._edgeObjs[e]; | |
| decrementOrRemoveEntry(this._preds[w], v); | |
| decrementOrRemoveEntry(this._sucs[v], w); | |
| delete this._in[w][e]; | |
| delete this._out[v][e]; | |
| this._edgeCount--; | |
| } | |
| return this; | |
| } | |
| inEdges(v, u) { | |
| var inV = this._in[v]; | |
| if (inV) { | |
| var edges = _.values(inV); | |
| if (!u) { | |
| return edges; | |
| } | |
| return _.filter(edges, function (edge) { | |
| return edge.v === u; | |
| }); | |
| } | |
| } | |
| outEdges(v, w) { | |
| var outV = this._out[v]; | |
| if (outV) { | |
| var edges = _.values(outV); | |
| if (!w) { | |
| return edges; | |
| } | |
| return _.filter(edges, function (edge) { | |
| return edge.w === w; | |
| }); | |
| } | |
| } | |
| nodeEdges(v, w) { | |
| var inEdges = this.inEdges(v, w); | |
| if (inEdges) { | |
| return inEdges.concat(this.outEdges(v, w)); | |
| } | |
| } | |
| } | |
| /* Number of nodes in the graph. Should only be changed by the implementation. */ | |
| Graph.prototype._nodeCount = 0; | |
| /* Number of edges in the graph. Should only be changed by the implementation. */ | |
| Graph.prototype._edgeCount = 0; | |
| function incrementOrInitEntry(map, k) { | |
| if (map[k]) { | |
| map[k]++; | |
| } else { | |
| map[k] = 1; | |
| } | |
| } | |
| function decrementOrRemoveEntry(map, k) { | |
| if (!--map[k]) { | |
| delete map[k]; | |
| } | |
| } | |
| function edgeArgsToId(isDirected, v_, w_, name) { | |
| var v = '' + v_; | |
| var w = '' + w_; | |
| if (!isDirected && v > w) { | |
| var tmp = v; | |
| v = w; | |
| w = tmp; | |
| } | |
| return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM + (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name); | |
| } | |
| function edgeArgsToObj(isDirected, v_, w_, name) { | |
| var v = '' + v_; | |
| var w = '' + w_; | |
| if (!isDirected && v > w) { | |
| var tmp = v; | |
| v = w; | |
| w = tmp; | |
| } | |
| var edgeObj = { v: v, w: w }; | |
| if (name) { | |
| edgeObj.name = name; | |
| } | |
| return edgeObj; | |
| } | |
| function edgeObjToId(isDirected, edgeObj) { | |
| return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name); | |
| } | |