Skip to content

Commit

Permalink
feat: use Kahn's algorithm for finding toposort (#312)
Browse files Browse the repository at this point in the history
  • Loading branch information
winprn authored Jan 3, 2025
1 parent 79133d0 commit ce3277b
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 46 deletions.
74 changes: 36 additions & 38 deletions packages/object/src/hashgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ export type { Vertex, Operation };

export type Hash = string;

export enum DepthFirstSearchState {
UNVISITED = 0,
VISITING = 1,
VISITED = 2,
}

export enum OperationType {
NOP = "-1",
}
Expand Down Expand Up @@ -219,39 +213,46 @@ export class HashGraph {
return hash;
}

depthFirstSearch(
origin: Hash,
subgraph: ObjectSet<Hash>,
visited: Map<Hash, number> = new Map(),
): Hash[] {
kahnsAlgorithm(origin: Hash, subgraph: ObjectSet<Hash>): Hash[] {
const result: Hash[] = [];
const inDegree = new Map<Hash, number>();
const queue: Hash[] = [];

for (const hash of subgraph.entries()) {
visited.set(hash, DepthFirstSearchState.UNVISITED);
inDegree.set(hash, 0);
}
const visit = (hash: Hash) => {
visited.set(hash, DepthFirstSearchState.VISITING);

const children = this.forwardEdges.get(hash) || [];
for (const [vertex, children] of this.forwardEdges) {
if (!inDegree.has(vertex)) continue;
for (const child of children) {
if (!subgraph.has(child)) continue;
if (visited.get(child) === DepthFirstSearchState.VISITING) {
log.error("::hashgraph::DFS: Cycle detected");
return;
}
if (visited.get(child) === undefined) {
log.error("::hashgraph::DFS: Undefined child");
return;
}
if (visited.get(child) === DepthFirstSearchState.UNVISITED) {
visit(child);
}
if (!inDegree.has(child)) continue;
inDegree.set(child, (inDegree.get(child) || 0) + 1);
}
}

result.push(hash);
visited.set(hash, DepthFirstSearchState.VISITED);
};
let head = 0;
queue.push(origin);
while (queue.length > 0) {
const current = queue[head];
head++;
if (!current) continue;

visit(origin);
result.push(current);

for (const child of this.forwardEdges.get(current) || []) {
if (!inDegree.has(child)) continue;
const inDegreeValue = inDegree.get(child) || 0;
inDegree.set(child, inDegreeValue - 1);
if (inDegreeValue - 1 === 0) {
queue.push(child);
}
}

if (head > queue.length / 2) {
queue.splice(0, head);
head = 0;
}
}

return result;
}
Expand All @@ -262,8 +263,7 @@ export class HashGraph {
origin: Hash = HashGraph.rootHash,
subgraph: ObjectSet<Hash> = new ObjectSet(this.vertices.keys()),
): Hash[] {
const result = this.depthFirstSearch(origin, subgraph);
result.reverse();
const result = this.kahnsAlgorithm(origin, subgraph);
if (!updateBitsets) return result;
this.reachablePredecessors.clear();
this.topoSortedIndex.clear();
Expand Down Expand Up @@ -468,18 +468,16 @@ export class HashGraph {
}
}

const visited = new Map<Hash, number>();
this.depthFirstSearch(
const topoOrder = this.kahnsAlgorithm(
HashGraph.rootHash,
new ObjectSet(this.vertices.keys()),
visited,
);

for (const vertex of this.getAllVertices()) {
if (!visited.has(vertex.hash)) {
if (!topoOrder.includes(vertex.hash)) {
return false;
}
}

return true;
}

Expand Down
16 changes: 8 additions & 8 deletions packages/object/tests/hashgraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe("HashGraph construction tests", () => {

const linearOps = obj2.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "add", value: 2 },
{ type: "add", value: 1 },
]);
});

Expand Down Expand Up @@ -170,8 +170,8 @@ describe("HashGraph for AddWinSet tests", () => {
const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "add", value: 2 },
{ type: "remove", value: 1 },
{ type: "add", value: 2 },
]);
});

Expand Down Expand Up @@ -204,8 +204,8 @@ describe("HashGraph for AddWinSet tests", () => {
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "add", value: 1 },
{ type: "remove", value: 5 },
{ type: "add", value: 10 },
{ type: "remove", value: 5 },
]);
});

Expand Down Expand Up @@ -235,9 +235,9 @@ describe("HashGraph for AddWinSet tests", () => {

const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "add", value: 1 },
{ type: "add", value: 2 },
{ type: "add", value: 1 },
]);
});

Expand Down Expand Up @@ -288,12 +288,12 @@ describe("HashGraph for AddWinSet tests", () => {

const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "add", value: 2 },
{ type: "add", value: 1 },
{ type: "add", value: 3 },
{ type: "remove", value: 1 },
{ type: "add", value: 3 },
]);
});

Expand Down Expand Up @@ -345,11 +345,11 @@ describe("HashGraph for AddWinSet tests", () => {
const linearOps = obj1.hashGraph.linearizeOperations();
expect(linearOps).toEqual([
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "add", value: 1 },
{ type: "remove", value: 2 },
{ type: "add", value: 3 },
{ type: "remove", value: 2 },
{ type: "remove", value: 1 },
{ type: "add", value: 3 },
{ type: "add", value: 2 },
{ type: "remove", value: 1 },
]);
Expand Down

0 comments on commit ce3277b

Please sign in to comment.