diff --git a/lib/graph.js b/lib/graph.js index d3e5bd4f..9bb4ff4a 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -69,34 +69,32 @@ 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() { +Graph.prototype.isDirected = function () { return this._isDirected; }; -Graph.prototype.isMultigraph = function() { +Graph.prototype.isMultigraph = function () { return this._isMultigraph; }; -Graph.prototype.isCompound = function() { +Graph.prototype.isCompound = function () { return this._isCompound; }; -Graph.prototype.setGraph = function(label) { +Graph.prototype.setGraph = function (label) { this._label = label; return this; }; -Graph.prototype.graph = function() { +Graph.prototype.graph = function () { return this._label; }; - /* === Node functions ========== */ -Graph.prototype.setDefaultNodeLabel = function(newDefault) { +Graph.prototype.setDefaultNodeLabel = function (newDefault) { if (!_.isFunction(newDefault)) { newDefault = _.constant(newDefault); } @@ -104,32 +102,32 @@ Graph.prototype.setDefaultNodeLabel = function(newDefault) { return this; }; -Graph.prototype.nodeCount = function() { +Graph.prototype.nodeCount = function () { return this._nodeCount; }; -Graph.prototype.nodes = function() { +Graph.prototype.nodes = function () { return _.keys(this._nodes); }; -Graph.prototype.sources = function() { +Graph.prototype.sources = function () { var self = this; - return _.filter(this.nodes(), function(v) { + return _.filter(this.nodes(), function (v) { return _.isEmpty(self._in[v]); }); }; -Graph.prototype.sinks = function() { +Graph.prototype.sinks = function () { var self = this; - return _.filter(this.nodes(), function(v) { + return _.filter(this.nodes(), function (v) { return _.isEmpty(self._out[v]); }); }; -Graph.prototype.setNodes = function(vs, value) { +Graph.prototype.setNodes = function (vs, value) { var args = arguments; var self = this; - _.each(vs, function(v) { + _.each(vs, function (v) { if (args.length > 1) { self.setNode(v, value); } else { @@ -139,7 +137,7 @@ Graph.prototype.setNodes = function(vs, value) { return this; }; -Graph.prototype.setNode = function(v, value) { +Graph.prototype.setNode = function (v, value) { if (_.has(this._nodes, v)) { if (arguments.length > 1) { this._nodes[v] = value; @@ -161,23 +159,25 @@ Graph.prototype.setNode = function(v, value) { return this; }; -Graph.prototype.node = function(v) { +Graph.prototype.node = function (v) { return this._nodes[v]; }; -Graph.prototype.hasNode = function(v) { +Graph.prototype.hasNode = function (v) { return _.has(this._nodes, v); }; -Graph.prototype.removeNode = function(v) { +Graph.prototype.removeNode = function (v) { var self = this; if (_.has(this._nodes, v)) { - var removeEdge = function(e) { self.removeEdge(self._edgeObjs[e]); }; + 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) { + _.each(this.children(v), function (child) { self.setParent(child); }); delete this._children[v]; @@ -193,7 +193,7 @@ Graph.prototype.removeNode = function(v) { return this; }; -Graph.prototype.setParent = function(v, parent) { +Graph.prototype.setParent = function (v, parent) { if (!this._isCompound) { throw new Error("Cannot set parent in a non-compound graph"); } @@ -203,12 +203,15 @@ Graph.prototype.setParent = function(v, parent) { } else { // Coerce parent to string parent += ""; - for (var ancestor = parent; + for ( + var ancestor = parent; !_.isUndefined(ancestor); - ancestor = this.parent(ancestor)) { + ancestor = this.parent(ancestor) + ) { if (ancestor === v) { - throw new Error("Setting " + parent+ " as parent of " + v + - " would create a cycle"); + throw new Error( + "Setting " + parent + " as parent of " + v + " would create a cycle" + ); } } @@ -222,11 +225,11 @@ Graph.prototype.setParent = function(v, parent) { return this; }; -Graph.prototype._removeFromParentsChildList = function(v) { +Graph.prototype._removeFromParentsChildList = function (v) { delete this._children[this._parent[v]][v]; }; -Graph.prototype.parent = function(v) { +Graph.prototype.parent = function (v) { if (this._isCompound) { var parent = this._parent[v]; if (parent !== GRAPH_NODE) { @@ -235,7 +238,7 @@ Graph.prototype.parent = function(v) { } }; -Graph.prototype.children = function(v) { +Graph.prototype.children = function (v) { if (_.isUndefined(v)) { v = GRAPH_NODE; } @@ -252,21 +255,21 @@ Graph.prototype.children = function(v) { } }; -Graph.prototype.predecessors = function(v) { +Graph.prototype.predecessors = function (v) { var predsV = this._preds[v]; if (predsV) { return _.keys(predsV); } }; -Graph.prototype.successors = function(v) { +Graph.prototype.successors = function (v) { var sucsV = this._sucs[v]; if (sucsV) { return _.keys(sucsV); } }; -Graph.prototype.neighbors = function(v) { +Graph.prototype.neighbors = function (v) { var preds = this.predecessors(v); if (preds) { return _.union(preds, this.successors(v)); @@ -283,23 +286,23 @@ Graph.prototype.isLeaf = function (v) { return neighbors.length === 0; }; -Graph.prototype.filterNodes = function(filter) { +Graph.prototype.filterNodes = function (filter) { var copy = new this.constructor({ directed: this._isDirected, multigraph: this._isMultigraph, - compound: this._isCompound + compound: this._isCompound, }); copy.setGraph(this.graph()); var self = this; - _.each(this._nodes, function(value, v) { + _.each(this._nodes, function (value, v) { if (filter(v)) { copy.setNode(v, value); } }); - _.each(this._edgeObjs, function(e) { + _.each(this._edgeObjs, function (e) { if (copy.hasNode(e.v) && copy.hasNode(e.w)) { copy.setEdge(e, self.edge(e)); } @@ -319,7 +322,7 @@ Graph.prototype.filterNodes = function(filter) { } if (this._isCompound) { - _.each(copy.nodes(), function(v) { + _.each(copy.nodes(), function (v) { copy.setParent(v, findParent(v)); }); } @@ -329,7 +332,7 @@ Graph.prototype.filterNodes = function(filter) { /* === Edge functions ========== */ -Graph.prototype.setDefaultEdgeLabel = function(newDefault) { +Graph.prototype.setDefaultEdgeLabel = function (newDefault) { if (!_.isFunction(newDefault)) { newDefault = _.constant(newDefault); } @@ -337,18 +340,18 @@ Graph.prototype.setDefaultEdgeLabel = function(newDefault) { return this; }; -Graph.prototype.edgeCount = function() { +Graph.prototype.edgeCount = function () { return this._edgeCount; }; -Graph.prototype.edges = function() { +Graph.prototype.edges = function () { return _.values(this._edgeObjs); }; -Graph.prototype.setPath = function(vs, value) { +Graph.prototype.setPath = function (vs, value) { var self = this; var args = arguments; - _.reduce(vs, function(v, w) { + _.reduce(vs, function (v, w) { if (args.length > 1) { self.setEdge(v, w, value); } else { @@ -363,7 +366,7 @@ Graph.prototype.setPath = function(vs, value) { * setEdge(v, w, [value, [name]]) * setEdge({ v, w, [name] }, [value]) */ -Graph.prototype.setEdge = function() { +Graph.prototype.setEdge = function () { var v, w, name, value; var valueSpecified = false; var arg0 = arguments[0]; @@ -409,7 +412,9 @@ Graph.prototype.setEdge = function() { this.setNode(v); this.setNode(w); - this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name); + 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. @@ -426,24 +431,27 @@ Graph.prototype.setEdge = function() { 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)); +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)); +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)); +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; @@ -459,32 +467,44 @@ Graph.prototype.removeEdge = function(v, w, name) { return this; }; -Graph.prototype.inEdges = function(v, u) { +Graph.prototype.getInEdges = 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; }); + return _.filter(edges, function (edge) { + return edge.v === u; + }); } }; -Graph.prototype.outEdges = function(v, w) { +Graph.prototype.inEdges = function (v, u) { + return this.isDirected() ? this.getInEdges(v, u) : this.nodeEdges(v, u); +}; + +Graph.prototype.getOutEdges = 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; }); + return _.filter(edges, function (edge) { + return edge.w === w; + }); } }; -Graph.prototype.nodeEdges = function(v, w) { - var inEdges = this.inEdges(v, w); +Graph.prototype.outEdges = function (v, w) { + return this.isDirected() ? this.getOutEdges(v, w) : this.nodeEdges(v, w); +}; + +Graph.prototype.nodeEdges = function (v, w) { + var inEdges = this.getInEdges(v, w, false); if (inEdges) { - return inEdges.concat(this.outEdges(v, w)); + return inEdges.concat(this.getOutEdges(v, w, false)); } }; @@ -497,7 +517,9 @@ function incrementOrInitEntry(map, k) { } function decrementOrRemoveEntry(map, k) { - if (!--map[k]) { delete map[k]; } + if (!--map[k]) { + delete map[k]; + } } function edgeArgsToId(isDirected, v_, w_, name) { @@ -520,7 +542,7 @@ function edgeArgsToObj(isDirected, v_, w_, name) { v = w; w = tmp; } - var edgeObj = { v: v, w: w }; + var edgeObj = { v: v, w: w }; if (name) { edgeObj.name = name; } diff --git a/test/alg/dijkstra-test.js b/test/alg/dijkstra-test.js index bfb77737..6f722fee 100644 --- a/test/alg/dijkstra-test.js +++ b/test/alg/dijkstra-test.js @@ -44,6 +44,20 @@ describe("alg.dijkstra", function() { }); }); + it("works for undirected graphs when edges have a different natural order", + function() { + var g = new Graph({ directed: false }); + g.setPath(["a", "b", "c"]); + g.setEdge("b", "d"); + expect(dijkstra(g, "d")).to.eql({ + a: { distance: 2, predecessor: "b" }, + b: { distance: 1, predecessor: "d" }, + c: { distance: 2, predecessor: "b" }, + d: { distance: 0 } + }); + } + ); + it("uses an optionally supplied weight function", function() { var g = new Graph(); g.setEdge("a", "b", 1);