dig
Version:
Graph algorithms
396 lines (345 loc) • 9.83 kB
JavaScript
// A directed graph (V, E) where V is a set of nodes and E is a set of edges.
dig.DiGraph = (function() {
// Returns the node object for u in V or throws an error if the node does
// node exist.
function _safeGetNode(g, u) {
var V = g._nodes;
if (!(u in V)) {
throw new Error("Node not in graph: " + u);
}
return V[u];
};
// Returns the edge object for (u, v) in E or throws an error if the edge
// does not exist.
function _safeGetEdge(g, u, v) {
var key = _edgeKey(u, v);
if (!(key in g._edges)) {
throw new Error("No such edge: (" + u + ", " + v + ")");
}
return g._edges[key];
}
// Creates a key that uniquely identifies the edge (u, v).
function _edgeKey(u, v) {
var uStr = u.toString();
var vStr = v.toString();
return uStr.length + ":" + uStr + vStr;
}
// Throws an error if this graph is immutable.
function _checkMutable(g) {
if (g._immutable) {
throw new Error("Graph is immutable!");
}
}
// Copies the first level of keys and values from src to dst.
function _shallowCopyAttrs(src, dst) {
if (Object.prototype.toString.call(src) !== '[object Object]') {
throw new Error("Attributes are not an object: " + src);
}
for (var k in src) {
dst[k] = src[k];
}
}
// Checks for equality of all keys and values on the two objects.
function _shallowEqual(lhs, rhs) {
var lhsKeys = dig_util_keys(lhs);
var rhsKeys = dig_util_keys(rhs);
if (lhsKeys.length !== rhsKeys.length) {
return false;
}
for (var k in lhs) {
if (lhs[k] !== rhs[k]) {
return false;
}
}
return true;
}
function _copyNodesTo(self, g) {
dig_util_forEach(self.nodes(), function(u) {
g.addNode(u, self.node(u));
});
};
function _nodesEqual(lhs, rhs) {
return lhs.order() === rhs.order() &&
dig_util_all(lhs.nodes(), function(u) {
return rhs.hasNode(u) && _shallowEqual(lhs.node(u), rhs.node(u));
});
}
function _edgesEqual(lhs, rhs) {
return lhs.size() === rhs.size() &&
dig_util_all(lhs.edges(), function(e) {
return rhs.hasEdge(e.from, e.to) &&
_shallowEqual(lhs.edge(e.from, e.to), rhs.edge(e.from, e.to));
});
}
function DiGraph() {
this._nodes = {};
this._edges = {};
this._order = 0;
this._size = 0;
this._immutable = false;
};
DiGraph.prototype = {
order: function() {
return this._order;
},
size: function() {
return this._size;
},
equals: function(g) {
if (g === this) {
return true;
}
return g instanceof DiGraph &&
_nodesEqual(this, g) &&
_edgesEqual(this, g);
},
copy: function() {
var g = new DiGraph();
var self = this;
_copyNodesTo(self, g);
dig_util_forEach(self.edges(), function(e) {
g.addEdge(e.from, e.to, self.edge(e.from, e.to));
});
return g;
},
immutable: function() {
var g = this.copy();
g._immutable = true;
return g;
},
nodes: function() {
return dig_util_keys(this._nodes);
},
hasNode: function(node) {
return node in this._nodes;
},
addNode: function(node, attrs) {
_checkMutable(this);
if (arguments.length < 1 || arguments.length > 2) {
throw new Error("Too many or too few arguments. Argument count: " + arguments.length);
}
var entry = this._nodes[node];
var added = false;
if (!entry) {
entry = this._nodes[node] = {
predecessors: {},
successors: {},
attrs: {}
};
this._order++;
added = true;
}
if (arguments.length >= 2) {
_shallowCopyAttrs(attrs, entry.attrs);
}
return added;
},
addNodes: function() {
_checkMutable(this);
for (var i = 0; i < arguments.length; ++i) {
this.addNode(arguments[i]);
}
},
node: function(u) {
var attrs = _safeGetNode(this, u).attrs;
if (this._immutable) {
var copy = {};
_shallowCopyAttrs(attrs, copy);
attrs = copy;
}
return attrs;
},
removeNode: function(node) {
_checkMutable(this);
var self = this;
if (this.hasNode(node)) {
dig_util_forEach(this.predecessors(node), function(i) {
self.removeEdge(i, node);
});
dig_util_forEach(this.successors(node), function(k) {
self.removeEdge(node, k);
});
delete this._nodes[node];
this._order--;
return true;
}
return false;
},
edges: function() {
var edges = [];
for (var k in this._edges) {
var edge = this._edges[k];
edge = {from: edge.from, to: edge.to, attrs: edge.attrs};
if (this._immutable) {
var copy = {};
_shallowCopyAttrs(edge.attrs, copy);
edge.attrs = copy;
}
edges.push(edge);
}
return edges;
},
hasEdge: function(u, v) {
return _edgeKey(u, v) in this._edges;
},
addEdge: function(u, v, attrs) {
_checkMutable(this);
if (arguments.length < 2 || arguments.length > 3) {
throw new Error("Too many or too few arguments. Argument count: " + arguments.length);
}
var fromNode = _safeGetNode(this, u);
var toNode = _safeGetNode(this, v);
var edgeKey = _edgeKey(u, v);
var entry = this._edges[edgeKey];
var added = false;
if (!entry) {
fromNode.successors[v] = true;
toNode.predecessors[u] = true;
entry = this._edges[edgeKey] = {
from: u,
to: v,
attrs: {}
};
this._size++;
added = true;
}
if (arguments.length >= 3) {
_shallowCopyAttrs(attrs, entry.attrs);
}
return added;
},
addPath: function() {
_checkMutable(this);
var prev, curr;
if (arguments.length > 1) {
prev = arguments[0];
for (var i = 1; i < arguments.length; ++i) {
curr = arguments[i];
this.addEdge(prev, curr);
prev = curr;
}
}
},
edge: function(u, v) {
return _safeGetEdge(this, u, v).attrs;
},
removeEdge: function(u, v) {
_checkMutable(this);
var edgeKey = _edgeKey(u, v);
if (edgeKey in this._edges) {
delete this._nodes[u].successors[v];
delete this._nodes[v].predecessors[u];
delete this._edges[edgeKey];
this._size--;
return true;
}
return false;
},
indegree: function(node) {
return this.inEdges(node).length;
},
outdegree: function(node) {
return this.outEdges(node).length;
},
degree: function(node) {
return this.indegree(node) + this.outdegree(node);
},
inEdges: function(node) {
var edges = [];
var preds = this.predecessors(node);
for (var i = 0; i < preds.length; i++) {
edges.push({from: preds[i], to: node});
}
return edges;
},
outEdges: function(node) {
var edges = [];
var sucs = this.successors(node);
for (var i = 0; i < sucs.length; i++) {
edges.push({from: node, to: sucs[i]});
};
return edges;
},
predecessors: function(node) {
return dig_util_keys(_safeGetNode(this, node).predecessors);
},
successors: function(node) {
return dig_util_keys(_safeGetNode(this, node).successors);
},
neighbors: function(node, direction) {
var entry = _safeGetNode(this, node);
var obj = {};
var indir = direction === "in" || direction === "both";
var outdir = direction === undefined || direction === "out" || direction === "both";
if (!indir && !outdir) {
throw new Error("Invalid direction specified: " + direction);
}
if (indir) {
for (var k in entry.predecessors) {
obj[k] = true;
}
}
if (outdir) {
for (var k in entry.successors) {
obj[k] = true;
}
}
return dig_util_keys(obj);
},
isAcyclic: function() {
var components = dig_alg_tarjan(this);
var self = this;
return dig_util_all(components, function(component) {
var v = component[0];
return component.length === 1 && !self.hasEdge(v, v);
});
},
sources: function() {
var sources = [];
var self = this;
dig_util_forEach(this.nodes(), function(i) {
if (self.indegree(i) == 0) {
sources.push(i);
}
});
return sources;
},
sinks: function() {
var sinks = [];
var self = this;
dig_util_forEach(this.nodes(), function(i) {
if (self.outdegree(i) === 0) {
sinks.push(i);
}
});
return sinks;
},
isDirected: function() {
return true;
},
undirected: function(edgeMerge) {
var g = new dig.UGraph();
var visitedEdges = {};
if (edgeMerge === undefined) {
edgeMerge = function() { return {}; }
}
_copyNodesTo(this, g);
var self = this;
dig_util_forEach(this.edges(), function(e) {
if (!(_edgeKey(e.from, e.to) in visitedEdges)) {
visitedEdges[_edgeKey(e.from, e.to)] = visitedEdges[_edgeKey(e.to, e.from)] = true;
var es = [e];
if (self.hasEdge(e.to, e.from)) {
es.push({from: e.to, to: e.from, attrs: self.edge(e.to, e.from)});
}
g.addEdge(e.from, e.to, edgeMerge(es));
}
});
return g;
},
toString: function() {
return dig_dot_write(this);
}
};
return DiGraph;
})();