Skip to content

Commit a5f8014

Browse files
Add tests, benchmark + misc fixes to filterNodes
1 parent 8905ce8 commit a5f8014

3 files changed

Lines changed: 134 additions & 24 deletions

File tree

lib/graph.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -270,27 +270,50 @@ Graph.prototype.neighbors = function(v) {
270270
}
271271
};
272272

273-
Graph.prototype.eachNode = function(func) {
274-
for (var id in this._nodes) {
275-
func(id, this._nodes[id]);
276-
}
277-
};
278-
279273
Graph.prototype.filterNodes = function(filter) {
280-
var copy = new this.constructor();
281-
copy.graph(this.graph());
282-
this.eachNode(function(u, value) {
283-
if (filter(u)) {
284-
copy.setNode(u, value);
285-
}
274+
var copy = new this.constructor({
275+
directed: this._isDirected,
276+
multigraph: this._isMultigraph,
277+
compound: this._isCompound
286278
});
287-
this.eachEdge(function(id, v, w, value) {
288-
if (copy.hasNode(v) && copy.hasNode(w)) {
289-
copy.setEdge(v, w, value);
279+
280+
copy.setGraph(this.graph());
281+
282+
_.each(this._nodes, function(value, v) {
283+
if (filter(v)) {
284+
copy.setNode(v, value);
290285
}
291-
});
286+
}, this);
287+
288+
_.each(this._edgeObjs, function(e) {
289+
if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
290+
copy.setEdge(e, this.edge(e));
291+
}
292+
}, this);
293+
294+
var self = this;
295+
var parents = {};
296+
function findParent(v) {
297+
var parent = self.parent(v);
298+
if (parent === undefined || copy.hasNode(parent)) {
299+
parents[v] = parent;
300+
return parent;
301+
} else if (parent in parents) {
302+
return parents[parent];
303+
} else {
304+
return findParent(parent);
305+
}
306+
}
307+
308+
if (this._isCompound) {
309+
_.each(copy.nodes(), function(v) {
310+
copy.setParent(v, findParent(v));
311+
});
312+
}
313+
292314
return copy;
293315
};
316+
294317
/* === Edge functions ========== */
295318

296319
Graph.prototype.setDefaultEdgeLabel = function(newDefault) {
@@ -451,14 +474,6 @@ Graph.prototype.nodeEdges = function(v, w) {
451474
}
452475
};
453476

454-
Graph.prototype.eachEdge = function(func) {
455-
var id, edge;
456-
for (id in this._edgeObjs) {
457-
edge=this._edgeObjs[id];
458-
func(id, edge.v, edge.w, this._edgeLabels[id]);
459-
}
460-
};
461-
462477
function incrementOrInitEntry(map, k) {
463478
if (_.has(map, k)) {
464479
map[k]++;

src/bench.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ NODE_SIZES.forEach(function(size) {
8888
g.sinks();
8989
});
9090

91+
runBenchmark("filterNodes all" + nameSuffix, function() {
92+
g.filterNodes(function() { return true; });
93+
});
94+
95+
runBenchmark("filterNodes none" + nameSuffix, function() {
96+
g.filterNodes(function() { return false; });
97+
});
98+
9199
runBenchmark("setNode" + nameSuffix, function() {
92100
g.setNode("key", "label");
93101
});

test/graph-test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,93 @@ describe("Graph", function() {
8989
});
9090
});
9191

92+
describe("filterNodes", function() {
93+
it("returns an identical graph when the filter selects everything", function() {
94+
g.setGraph("graph label");
95+
g.setNode("a", 123);
96+
g.setPath(["a", "b", "c"]);
97+
g.setEdge("a", "c", 456);
98+
var g2 = g.filterNodes(function() { return true; });
99+
expect(_.sortBy(g2.nodes())).eqls(["a", "b", "c"]);
100+
expect(_.sortBy(g2.successors("a"))).eqls(["b", "c"]);
101+
expect(_.sortBy(g2.successors("b"))).eqls(["c"]);
102+
expect(g2.node("a")).eqls(123);
103+
expect(g2.edge("a", "c")).eqls(456);
104+
expect(g2.graph()).eqls("graph label");
105+
});
106+
107+
it("returns an empty graph when the filter selects nothing", function() {
108+
g.setPath(["a", "b", "c"]);
109+
var g2 = g.filterNodes(function() { return false; });
110+
expect(g2.nodes()).eqls([]);
111+
expect(g2.edges()).eqls([]);
112+
});
113+
114+
it("only includes nodes for which the filter returns true", function() {
115+
g.setNodes(["a", "b"]);
116+
var g2 = g.filterNodes(function(v) { return v === "a"; });
117+
expect(g2.nodes()).eqls(["a"]);
118+
});
119+
120+
it("removes edges that are connected to removed nodes", function() {
121+
g.setEdge("a", "b");
122+
var g2 = g.filterNodes(function(v) { return v === "a"; });
123+
expect(_.sortBy(g2.nodes())).eqls(["a"]);
124+
expect(g2.edges()).eqls([]);
125+
});
126+
127+
it("preserves the directed option", function() {
128+
g = new Graph({ directed: true });
129+
expect(g.filterNodes(function() { return true; }).isDirected()).to.be.true;
130+
131+
g = new Graph({ directed: false });
132+
expect(g.filterNodes(function() { return true; }).isDirected()).to.be.false;
133+
});
134+
135+
it("preserves the multigraph option", function() {
136+
g = new Graph({ multigraph: true });
137+
expect(g.filterNodes(function() { return true; }).isMultigraph()).to.be.true;
138+
139+
g = new Graph({ multigraph: false });
140+
expect(g.filterNodes(function() { return true; }).isMultigraph()).to.be.false;
141+
});
142+
143+
it("preserves the compound option", function() {
144+
g = new Graph({ compound: true });
145+
expect(g.filterNodes(function() { return true; }).isCompound()).to.be.true;
146+
147+
g = new Graph({ compound: false });
148+
expect(g.filterNodes(function() { return true; }).isCompound()).to.be.false;
149+
});
150+
151+
it("includes subgraphs", function() {
152+
g = new Graph({ compound: true });
153+
g.setParent("a", "parent");
154+
155+
var g2 = g.filterNodes(function() { return true; });
156+
expect(g2.parent("a")).eqls("parent");
157+
});
158+
159+
it("includes multi-level subgraphs", function() {
160+
g = new Graph({ compound: true });
161+
g.setParent("a", "parent");
162+
g.setParent("parent", "root");
163+
164+
var g2 = g.filterNodes(function() { return true; });
165+
expect(g2.parent("a")).eqls("parent");
166+
expect(g2.parent("parent")).eqls("root");
167+
});
168+
169+
it("promotes a node to a higher subgraph if its parent is not included", function() {
170+
g = new Graph({ compound: true });
171+
g.setParent("a", "parent");
172+
g.setParent("parent", "root");
173+
174+
var g2 = g.filterNodes(function(v) { return v !== "parent"; });
175+
expect(g2.parent("a")).eqls("root");
176+
});
177+
});
178+
92179
describe("setNodes", function() {
93180
it("creates multiple nodes", function() {
94181
g.setNodes(["a", "b", "c"]);

0 commit comments

Comments
 (0)