Skip to content

Commit

Permalink
Cache topological sort for preformance (no incremental update to the …
Browse files Browse the repository at this point in the history
…sort)
  • Loading branch information
SegFaultx64 committed Dec 16, 2020
1 parent 39cf867 commit b4450e5
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 1 deletion.
24 changes: 23 additions & 1 deletion src/directedAcyclicGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export class CycleError<T> extends Error {
}
}
export default class DirectedAcyclicGraph<T> extends DirectedGraph<T> {
private _topologicallySortedNodes?: Array<T>;

static fromDirectedGraph<T>(graph: DirectedGraph<T>): DirectedAcyclicGraph<T> {
if (!graph.isAcyclic()) {
throw new CycleError("Can't convert that graph to a DAG because it contains a cycle")
Expand All @@ -27,11 +29,26 @@ export default class DirectedAcyclicGraph<T> extends DirectedGraph<T> {
if (this.wouldAddingEdgeCreateCyle(node1Identity, node2Identity)) {
throw new CycleError(`Can't add edge from ${node1Identity} to ${node2Identity} it would create a cycle`)
}

// Invalidate cache of toposorted nodes
this._topologicallySortedNodes = undefined;
super.addEdge(node1Identity, node2Identity, true)
}

// Maintain topo sort when inserting node
insert(node: T): string {
if (this._topologicallySortedNodes)
this._topologicallySortedNodes = [node, ...this._topologicallySortedNodes];

return super.insert(node)
}

// This is an implementation of Kahn's algorithim
topologicallySortedNodes(): Array<T> {
if (this._topologicallySortedNodes != undefined) {
return this._topologicallySortedNodes;
}

const nodeIndices = Array.from(this.nodes.keys());
const nodeInDegrees = new Map(Array.from(this.nodes.keys()).map(n => [n, this.inDegreeOfNode(n)]))

Expand All @@ -40,7 +57,9 @@ export default class DirectedAcyclicGraph<T> extends DirectedGraph<T> {
let toSearch = Array.from(nodeInDegrees).filter(pair => pair[1] === 0)

if (toSearch.length === this.nodes.size) {
return Array.from(this.nodes.values())
const arrayOfNodes = Array.from(this.nodes.values());
this._topologicallySortedNodes = arrayOfNodes
return arrayOfNodes
}

let toReturn: Array<T> = []
Expand All @@ -63,6 +82,9 @@ export default class DirectedAcyclicGraph<T> extends DirectedGraph<T> {
})
}

// Update cache
this._topologicallySortedNodes = toReturn;

// we shouldn't need to account for the error case of there being a cycle because it shouldn't
// be possible to instantiate this class in a state (or put it in a state) where there is a cycle.

Expand Down
8 changes: 8 additions & 0 deletions test/directedAcyclicGraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ describe("Directed Acyclic Graph", () => {

expect([{ name: 'C' }, { name: 'D' }]).toContainEqual(topoList3[1])
expect([{ name: 'C' }, { name: 'D' }]).toContainEqual(topoList3[2])

graph.insert({ name: 'F' })

const topoList4 = graph.topologicallySortedNodes();

expect(topoList4).toContainEqual({ name: 'F' })
expect([{ name: 'A' }, { name: 'F' }]).toContainEqual(topoList4[0])
expect([{ name: 'A' }, { name: 'F' }]).toContainEqual(topoList4[1])
})

})

0 comments on commit b4450e5

Please sign in to comment.