533 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			533 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| var _ = require("./lodash");
 | |
| 
 | |
| module.exports = Graph;
 | |
| 
 | |
| 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.
 | |
| 
 | |
| function Graph(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 = {};
 | |
| }
 | |
| 
 | |
| /* 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;
 | |
| 
 | |
| 
 | |
| /* === Graph functions ========= */
 | |
| 
 | |
| Graph.prototype.isDirected = function() {
 | |
|   return this._isDirected;
 | |
| };
 | |
| 
 | |
| Graph.prototype.isMultigraph = function() {
 | |
|   return this._isMultigraph;
 | |
| };
 | |
| 
 | |
| Graph.prototype.isCompound = function() {
 | |
|   return this._isCompound;
 | |
| };
 | |
| 
 | |
| Graph.prototype.setGraph = function(label) {
 | |
|   this._label = label;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| Graph.prototype.graph = function() {
 | |
|   return this._label;
 | |
| };
 | |
| 
 | |
| 
 | |
| /* === Node functions ========== */
 | |
| 
 | |
| Graph.prototype.setDefaultNodeLabel = function(newDefault) {
 | |
|   if (!_.isFunction(newDefault)) {
 | |
|     newDefault = _.constant(newDefault);
 | |
|   }
 | |
|   this._defaultNodeLabelFn = newDefault;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| Graph.prototype.nodeCount = function() {
 | |
|   return this._nodeCount;
 | |
| };
 | |
| 
 | |
| Graph.prototype.nodes = function() {
 | |
|   return _.keys(this._nodes);
 | |
| };
 | |
| 
 | |
| Graph.prototype.sources = function() {
 | |
|   var self = this;
 | |
|   return _.filter(this.nodes(), function(v) {
 | |
|     return _.isEmpty(self._in[v]);
 | |
|   });
 | |
| };
 | |
| 
 | |
| Graph.prototype.sinks = function() {
 | |
|   var self = this;
 | |
|   return _.filter(this.nodes(), function(v) {
 | |
|     return _.isEmpty(self._out[v]);
 | |
|   });
 | |
| };
 | |
| 
 | |
| Graph.prototype.setNodes = function(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;
 | |
| };
 | |
| 
 | |
| Graph.prototype.setNode = function(v, value) {
 | |
|   if (_.has(this._nodes, v)) {
 | |
|     if (arguments.length > 1) {
 | |
|       this._nodes[v] = value;
 | |
|     }
 | |
|     return this;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
| };
 | |
| 
 | |
| Graph.prototype.node = function(v) {
 | |
|   return this._nodes[v];
 | |
| };
 | |
| 
 | |
| Graph.prototype.hasNode = function(v) {
 | |
|   return _.has(this._nodes, v);
 | |
| };
 | |
| 
 | |
| Graph.prototype.removeNode =  function(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;
 | |
| };
 | |
| 
 | |
| Graph.prototype.setParent = function(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;
 | |
| };
 | |
| 
 | |
| Graph.prototype._removeFromParentsChildList = function(v) {
 | |
|   delete this._children[this._parent[v]][v];
 | |
| };
 | |
| 
 | |
| Graph.prototype.parent = function(v) {
 | |
|   if (this._isCompound) {
 | |
|     var parent = this._parent[v];
 | |
|     if (parent !== GRAPH_NODE) {
 | |
|       return parent;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.children = function(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 [];
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.predecessors = function(v) {
 | |
|   var predsV = this._preds[v];
 | |
|   if (predsV) {
 | |
|     return _.keys(predsV);
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.successors = function(v) {
 | |
|   var sucsV = this._sucs[v];
 | |
|   if (sucsV) {
 | |
|     return _.keys(sucsV);
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.neighbors = function(v) {
 | |
|   var preds = this.predecessors(v);
 | |
|   if (preds) {
 | |
|     return _.union(preds, this.successors(v));
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.isLeaf = function (v) {
 | |
|   var neighbors;
 | |
|   if (this.isDirected()) {
 | |
|     neighbors = this.successors(v);
 | |
|   } else {
 | |
|     neighbors = this.neighbors(v);
 | |
|   }
 | |
|   return neighbors.length === 0;
 | |
| };
 | |
| 
 | |
| Graph.prototype.filterNodes = function(filter) {
 | |
|   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) {
 | |
|     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 ========== */
 | |
| 
 | |
| Graph.prototype.setDefaultEdgeLabel = function(newDefault) {
 | |
|   if (!_.isFunction(newDefault)) {
 | |
|     newDefault = _.constant(newDefault);
 | |
|   }
 | |
|   this._defaultEdgeLabelFn = newDefault;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| Graph.prototype.edgeCount = function() {
 | |
|   return this._edgeCount;
 | |
| };
 | |
| 
 | |
| Graph.prototype.edges = function() {
 | |
|   return _.values(this._edgeObjs);
 | |
| };
 | |
| 
 | |
| Graph.prototype.setPath = function(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])
 | |
|  */
 | |
| Graph.prototype.setEdge = function() {
 | |
|   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);
 | |
| 
 | |
|   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;
 | |
| };
 | |
| 
 | |
| Graph.prototype.edge = function(v, w, name) {
 | |
|   var e = (arguments.length === 1
 | |
|     ? edgeObjToId(this._isDirected, arguments[0])
 | |
|     : edgeArgsToId(this._isDirected, v, w, name));
 | |
|   return this._edgeLabels[e];
 | |
| };
 | |
| 
 | |
| Graph.prototype.hasEdge = function(v, w, name) {
 | |
|   var e = (arguments.length === 1
 | |
|     ? edgeObjToId(this._isDirected, arguments[0])
 | |
|     : edgeArgsToId(this._isDirected, v, w, name));
 | |
|   return _.has(this._edgeLabels, e);
 | |
| };
 | |
| 
 | |
| Graph.prototype.removeEdge = function(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;
 | |
| };
 | |
| 
 | |
| Graph.prototype.inEdges = function(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; });
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.outEdges = function(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; });
 | |
|   }
 | |
| };
 | |
| 
 | |
| Graph.prototype.nodeEdges = function(v, w) {
 | |
|   var inEdges = this.inEdges(v, w);
 | |
|   if (inEdges) {
 | |
|     return inEdges.concat(this.outEdges(v, w));
 | |
|   }
 | |
| };
 | |
| 
 | |
| 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);
 | |
| }
 |