From 61057f0f8b717e79a9683f52006e40607d880a92 Mon Sep 17 00:00:00 2001 From: h3lio5 Date: Sun, 18 Aug 2024 13:43:28 +0530 Subject: [PATCH] refactor: AddWinsSet and HashGraph --- packages/crdt/package.json | 2 +- .../crdt/src/builtins/AddWinsSet/index.ts | 56 ++-- packages/crdt/tests/AddWinsSet.test.ts | 220 ++++++------ packages/crdt/vitest.config.js | 10 - packages/object/src/hashgraph.ts | 312 +++++++++--------- 5 files changed, 298 insertions(+), 302 deletions(-) delete mode 100644 packages/crdt/vitest.config.js diff --git a/packages/crdt/package.json b/packages/crdt/package.json index dfb7ae1d..c2a8a374 100644 --- a/packages/crdt/package.json +++ b/packages/crdt/package.json @@ -24,7 +24,7 @@ "build": "tsc -b", "clean": "rm -rf dist/ node_modules/", "prepack": "tsc -b", - "test": "vitest --reporter=verbose" + "test": "vitest" }, "dependencies": {}, "devDependencies": { diff --git a/packages/crdt/src/builtins/AddWinsSet/index.ts b/packages/crdt/src/builtins/AddWinsSet/index.ts index cf69bdc0..9fa15e7d 100644 --- a/packages/crdt/src/builtins/AddWinsSet/index.ts +++ b/packages/crdt/src/builtins/AddWinsSet/index.ts @@ -1,24 +1,38 @@ /// GSet with support for state and op changes -export class AddWinsSet { - private _addSet: Set; - private _removeSet: Set; - - constructor(addSet: Set, removeSet: Set) { - this._addSet = addSet; - this._removeSet = removeSet; - } - - add(element: T): void { - this._addSet.add(element); - } - - remove(element: T): void { - this._removeSet.add(element); - } +export class AddWinsSet { + private state: Map; + + constructor() { + this.state = new Map(); + } + + add(value: T): void { + this.state.set(value, (this.state.get(value) || 0) + 1); + } + + remove(value: T): void { + // We don't actually remove, just increment the counter + this.add(value); + } + + getValue(value: T): number { + return this.state.get(value) || 0; + } + + isInSet(value: T): boolean { + const count = this.getValue(value); + return count > 0 && count % 2 === 1; + } + + values(): T[] { + return Array.from(this.state.entries()) + .filter(([_, count]) => count % 2 === 1) + .map(([value, _]) => value); + } - read(): T { - const addResult = Array.from(this._addSet).reduce((acc, val) => acc + val, 0); - const removeResult = Array.from(this._addSet).reduce((acc, val) => acc + val, 0); - return (addResult - removeResult) as T; + merge(other: AddWinsSet): void { + for (const [value, count] of other.state) { + this.state.set(value, Math.max(this.getValue(value), count)); } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/packages/crdt/tests/AddWinsSet.test.ts b/packages/crdt/tests/AddWinsSet.test.ts index 452f1ee7..9b761cbe 100644 --- a/packages/crdt/tests/AddWinsSet.test.ts +++ b/packages/crdt/tests/AddWinsSet.test.ts @@ -2,75 +2,80 @@ import { describe, test, expect, beforeEach } from "vitest"; import { AddWinsSet } from "../src/builtins/AddWinsSet"; import { ActionType, HashGraph } from "../../object/src/hashgraph"; import { TopologyObject } from "../../object/src"; -import exp from "constants"; -interface IAddWinsCRO extends TopologyObject { +interface IAddWinsCRO extends TopologyObject { addWinsSet: AddWinsSet; - hashGraph: HashGraph; + hashGraph: HashGraph>; add(element: T): void; remove(element: T): void; - resolveConflicts(op1: O, op2: O): ActionType; + resolveConflicts(op1: Operation, op2: Operation): ActionType; } -// Define a generic Operation interface -interface Operation { - type: string; // The type field can be any string - value: any; // The value field can be of any type +enum OperationType { + Add, + Remove } -class AddWinsCRO extends TopologyObject implements IAddWinsCRO { +class Operation { + constructor( + public readonly type: OperationType, + public readonly value: T + ) { } +} + + +class AddWinsCRO extends TopologyObject implements IAddWinsCRO { addWinsSet: AddWinsSet; - hashGraph: HashGraph; + hashGraph: HashGraph>; constructor(peerId: string) { super(peerId); - this.addWinsSet = new AddWinsSet(new Array(), new Array()); - this.hashGraph = new HashGraph(this.resolveConflicts); + this.addWinsSet = new AddWinsSet(); + this.hashGraph = new HashGraph>(this.resolveConflicts); } - resolveConflicts(op1: Operation, op2: Operation): ActionType { + resolveConflicts(op1: Operation, op2: Operation): ActionType { if (op1.type !== op2.type && op1.value === op2.value) { - return op1.type === "ADD" ? ActionType.DropRight : ActionType.DropLeft; + return op1.type === OperationType.Add ? ActionType.DropRight : ActionType.DropLeft; } return ActionType.Nop; } - add(element: T): void { - this.addWinsSet.add(element); + add(value: T): void { + const op = new Operation(OperationType.Add, value); + this.addWinsSet.add(value); + this.hashGraph.addVertex(op, this.hashGraph.getFrontier()); } - remove(element: T): void { - this.addWinsSet.remove(element); + remove(value: T): void { + const op = new Operation(OperationType.Remove, value); + this.addWinsSet.remove(value); + this.hashGraph.addVertex(op, this.hashGraph.getFrontier()); } merge(other: TopologyObject): void { - + } - read(): T { - const ops = this.hashGraph.linearizeOps(); - for (const op of ops) { - switch (op.type) { - case "ADD": - this.add(op.value); - break; - case "REMOVE": - this.remove(op.value) - break; + read(): T[] { + const operations = this.hashGraph.linearizeOps(); + const tempCounter = new AddWinsSet(); + + for (const op of operations) { + if (op.type === OperationType.Add) { + tempCounter.add(op.value); + } else { + tempCounter.remove(op.value); } } - return this.addWinsSet.read(); + + return tempCounter.values(); + } } -// define Operation type -type OP = - {type: "ADD", value: number} - | {type: "REMOVE", value: number} -; - describe("HashGraph for AddWinSet tests", () => { - let cro: AddWinsCRO; + let cro: AddWinsCRO; beforeEach(() => { cro = new AddWinsCRO("peer0"); @@ -81,21 +86,19 @@ describe("HashGraph for AddWinSet tests", () => { /* V1:ADD(1) <- V2:REMOVE(1) */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); let linearOps = cro.hashGraph.linearizeOps(); expect(linearOps).toEqual([op1]); - + // Add second vertex - let op2: OP = {type: "REMOVE", value: 1}; + let op2: Operation = new Operation(OperationType.Remove, 1); let deps2: string[] = [vertexHash1]; - let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); linearOps = cro.hashGraph.linearizeOps(); let orderArray = cro.hashGraph.topologicalSort(); expect(linearOps).toEqual([op1, op2]); - - }); test("Test: Add Two Concurrent Vertices With Same Value", () => { @@ -105,33 +108,28 @@ describe("HashGraph for AddWinSet tests", () => { \ _ V3:ADD(1) */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1);; let deps1: string[] = []; let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); let linearOps = cro.hashGraph.linearizeOps(); expect(linearOps).toEqual([op1]); - + // Add second vertex - let op2: OP = {type: "REMOVE", value: 1}; + let op2: Operation = new Operation(OperationType.Remove, 1); let deps2: string[] = [vertexHash1]; let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); linearOps = cro.hashGraph.linearizeOps(); - expect(linearOps).toEqual([op1, op2]); + expect(linearOps).toEqual([op1, op2]); // Add the third vertex V3 concurrent with V2 - let op3: OP = {type: "ADD", value: 1}; + let op3: Operation = new Operation(OperationType.Add, 1); let deps3: string[] = [vertexHash1]; let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); linearOps = cro.hashGraph.linearizeOps(); expect(linearOps).toEqual([op1, op3]); - - // the read value should be 2 ,i.e., ADD(1) + (ADD(1) + REMOVE(1)) => ADD(1) + ADD(1) => 1 + 1 => 2 - let readValue = cro.read(); - expect(readValue).toBe(2); - }); test("Test: Add Two Concurrent Vertices With Different Values", () => { @@ -141,98 +139,98 @@ describe("HashGraph for AddWinSet tests", () => { \ _ V3:ADD(2) */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); let linearOps = cro.hashGraph.linearizeOps(); expect(linearOps).toEqual([op1]); - + // Add second vertex - let op2: OP = {type: "REMOVE", value: 1}; + let op2: Operation = new Operation(OperationType.Remove, 1); let deps2: string[] = [vertexHash1]; let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); linearOps = cro.hashGraph.linearizeOps(); - expect(linearOps).toEqual([op1, op2]); + expect(linearOps).toEqual([op1, op2]); // Add the third vertex V3 concurrent with V2 - let op3: OP = {type: "ADD", value: 3}; + let op3: Operation = new Operation(OperationType.Add, 3); let deps3: string[] = [vertexHash1]; - let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); linearOps = cro.hashGraph.linearizeOps(); - expect([[op1, op2, op3],[op1, op3, op2]]).toContainEqual(linearOps); - }); + expect([[op1, op2, op3], [op1, op3, op2]]).toContainEqual(linearOps); + }); - test("Test: Tricky Case", () => { + test("Test: Tricky Case", () => { /* ___ V2:REMOVE(1) <- V4:ADD(10) V1:ADD(1) / \ ___ V3:ADD(1) <- V5:REMOVE(5) */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); - + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + // Add second vertex - let op2: OP = {type: "REMOVE", value: 1}; + let op2: Operation = new Operation(OperationType.Remove, 1); let deps2: string[] = [vertexHash1]; let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); // Add the third vertex V3 concurrent with V2 - let op3: OP = {type: "ADD", value: 1}; + let op3: Operation = new Operation(OperationType.Add, 1); let deps3: string[] = [vertexHash1]; - let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); // Add the vertex V4 with dependency on V2 - let op4: OP = {type: "ADD", value: 10}; + let op4: Operation = new Operation(OperationType.Add, 10); let deps4: string[] = [vertexHash2]; let vertexHash4 = cro.hashGraph.addVertex(op4, deps4); // Add the vertex V5 with dependency on V3 - let op5: OP = {type: "REMOVE", value: 5}; + let op5: Operation = new Operation(OperationType.Remove, 5); let deps5: string[] = [vertexHash3]; let vertexHash5 = cro.hashGraph.addVertex(op5, deps5); const linearOps = cro.hashGraph.linearizeOps(); expect([[op1, op4, op3, op5], [op1, op3, op5, op4]]).toContainEqual(linearOps); - - }); - test("Test: Yuta Papa's Case", () => { + }); + + test("Test: Yuta Papa's Case", () => { /* ___ V2:REMOVE(1) <- V4:ADD(2) V1:ADD(1) / \ ___ V3:REMOVE(2) <- V5:ADD(1) */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); - + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + // Add second vertex - let op2: OP = {type: "REMOVE", value: 1}; + let op2: Operation = new Operation(OperationType.Remove, 1); let deps2: string[] = [vertexHash1]; let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); // Add the third vertex V3 concurrent with V2 - let op3: OP = {type: "REMOVE", value: 2}; + let op3: Operation = new Operation(OperationType.Remove, 2); let deps3: string[] = [vertexHash1]; - let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); // Add the vertex V4 with dependency on V2 - let op4: OP = {type: "ADD", value: 2}; + let op4: Operation = new Operation(OperationType.Add, 2); let deps4: string[] = [vertexHash2]; let vertexHash4 = cro.hashGraph.addVertex(op4, deps4); // Add the vertex V5 with dependency on V3 - let op5: OP = {type: "ADD", value: 1}; + let op5: Operation = new Operation(OperationType.Add, 1); let deps5: string[] = [vertexHash3]; let vertexHash5 = cro.hashGraph.addVertex(op5, deps5); const linearOps = cro.hashGraph.linearizeOps(); expect([[op1, op4, op5], [op1, op5, op4]]).toContainEqual(linearOps); - }); + }); - test("Test: Mega Complex Case", () => { + test("Test: Mega Complex Case", () => { /* __ V6:ADD(3) / @@ -241,49 +239,49 @@ describe("HashGraph for AddWinSet tests", () => { V1:ADD(1)/ / \ / \ ___ V4:RM(2) <-- V5:ADD(2) <-- V9:RM(1) - */ + */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); // Add second vertex - let op2: OP = {type: "ADD", value: 1}; + let op2: Operation = new Operation(OperationType.Add, 1); let deps2: string[] = [vertexHash1]; let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); // Add the third vertex V3 concurrent with V2 - let op3: OP = {type: "REMOVE", value: 2}; + let op3: Operation = new Operation(OperationType.Remove, 2); let deps3: string[] = [vertexHash2]; - let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); // Add the vertex V4 with dependency on V2 - let op4: OP = {type: "REMOVE", value: 2}; + let op4: Operation = new Operation(OperationType.Remove, 2); let deps4: string[] = [vertexHash1]; let vertexHash4 = cro.hashGraph.addVertex(op4, deps4); // Add the vertex V5 -> [V4] - let op5: OP = {type: "ADD", value: 2}; + let op5: Operation = new Operation(OperationType.Add, 2); let deps5: string[] = [vertexHash4]; let vertexHash5 = cro.hashGraph.addVertex(op5, deps5); // Add the vertex V6 ->[V3] - let op6: OP = {type: "ADD", value: 3}; + let op6: Operation = new Operation(OperationType.Add, 3); let deps6: string[] = [vertexHash3]; let vertexHash6 = cro.hashGraph.addVertex(op6, deps6); // Add the vertex V7 -> [V3] - let op7: OP = {type: "REMOVE", value: 1}; + let op7: Operation = new Operation(OperationType.Remove, 1); let deps7: string[] = [vertexHash3]; let vertexHash7 = cro.hashGraph.addVertex(op7, deps7); // Add the vertex V8 -> [V7, V5] - let op8: OP = {type: "REMOVE", value: 3}; + let op8: Operation = new Operation(OperationType.Remove, 3); let deps8: string[] = [vertexHash7, vertexHash5]; let vertexHash8 = cro.hashGraph.addVertex(op8, deps8); // Add the vertex V9 -> [V5] - let op9: OP = {type: "REMOVE", value: 1}; + let op9: Operation = new Operation(OperationType.Remove, 1); let deps9: string[] = [vertexHash5]; let vertexHash9 = cro.hashGraph.addVertex(op9, deps9); let linearOps = cro.hashGraph.linearizeOps(); expect([[op1, op2, op6, op7, op4, op5]]).toContainEqual(linearOps); - }); + }); - test("Test: Jaoa's latest brain teaser", () => { + test("Test: Jaoa's latest brain teaser", () => { /* __ V2:Add(2) <------------\ @@ -291,33 +289,33 @@ describe("HashGraph for AddWinSet tests", () => { \__ V3:RM(2) <- V4:RM(2) <--/ */ - let op1: OP = {type: "ADD", value: 1}; + let op1: Operation = new Operation(OperationType.Add, 1); let deps1: string[] = []; - let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1); // Add the second vertex V2 <- [V1] - let op2: OP = {type: "ADD", value: 2}; + let op2: Operation = new Operation(OperationType.Add, 2); let deps2: string[] = [vertexHash1]; - let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); - + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2); + // Add the third vertex V3 <- [V1] - let op3: OP = {type: "REMOVE", value: 2}; + let op3: Operation = new Operation(OperationType.Remove, 2); let deps3: string[] = [vertexHash1]; - let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3); // Add the fourth vertex V4 <- [V3] - let op4: OP = {type: "REMOVE", value: 2}; + let op4: Operation = new Operation(OperationType.Remove, 2); let deps4: string[] = [vertexHash3]; - let vertexHash4 = cro.hashGraph.addVertex(op4, deps4); + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4); // Add the fifth vertex V5 <- [V2, V4] - let op5: OP = {type: "REMOVE", value: 2}; + let op5: Operation = new Operation(OperationType.Remove, 2); let deps5: string[] = [vertexHash2, vertexHash4]; - let vertexHash5 = cro.hashGraph.addVertex(op5, deps5); + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5); const linearOps = cro.hashGraph.linearizeOps(); expect(linearOps).toEqual([op1, op2, op5]); - }); + }); }); // V1, V3, V4, V2, V5 diff --git a/packages/crdt/vitest.config.js b/packages/crdt/vitest.config.js deleted file mode 100644 index 7226c684..00000000 --- a/packages/crdt/vitest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -// vitest.config.js -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - silent: false, - reporters: ['verbose'], - }, -}); \ No newline at end of file diff --git a/packages/object/src/hashgraph.ts b/packages/object/src/hashgraph.ts index 618f57d1..ea363e56 100644 --- a/packages/object/src/hashgraph.ts +++ b/packages/object/src/hashgraph.ts @@ -2,204 +2,198 @@ import { sha256 } from "js-sha256"; type Hash = string; -interface Operation { - type: string; - value: any; -} - -class Vertex { - constructor( - public readonly hash: Hash, - public readonly operation: T, - public readonly dependencies: Set - ) {} +class Vertex { + constructor( + public readonly hash: Hash, + public readonly operation: T, + public readonly dependencies: Set + ) { } } export enum ActionType { - DropLeft, - DropRight, - Nop, - Swap + DropLeft, + DropRight, + Nop, + Swap } +export class HashGraph { + private vertices: Map> = new Map(); + private frontier: Set = new Set(); + private forwardEdges: Map> = new Map(); + // private resolveConflicts: (op1: T, op2: T) => ActionType; + private root: Hash = ""; -export class HashGraph { - private vertices: Map> = new Map(); - private frontier: Set = new Set(); - private forwardEdges: Map> = new Map(); - // private resolveConflicts: (op1: T, op2: T) => ActionType; - private root: Hash = ""; + constructor(private resolveConflicts: (op1: T, op2: T) => ActionType) { } - constructor(private resolveConflicts: (op1: T, op2: T) => ActionType) {} + // Time complexity: O(1), Space complexity: O(1) + computeHash(op: T, dependencies: Hash[]): Hash { + const serialized = JSON.stringify({ op, dependencies }); + return sha256(serialized); + } - // Time complexity: O(1), Space complexity: O(1) - computeHash(op: T, dependencies: Hash[]): Hash { - const serialized = JSON.stringify({ op, dependencies }); - return sha256(serialized); + // Time complexity: O(d), where d is the number of dependencies + // Space complexity: O(d) + addVertex(operation: T, dependencies: Hash[] = []): Hash { + const hash = this.computeHash(operation, dependencies); + if (this.vertices.has(hash)) { + return hash; // Vertex already exists } - // Time complexity: O(d), where d is the number of dependencies - // Space complexity: O(d) - addVertex(operation: T, dependencies: Hash[] = []): Hash { - const hash = this.computeHash(operation, dependencies); - if (this.vertices.has(hash)) { - return hash; // Vertex already exists - } + const vertex = new Vertex(hash, operation, new Set(dependencies)); + this.vertices.set(hash, vertex); + this.frontier.add(hash); + + // update root + if (dependencies.length === 0) { + this.root = hash; + } + // Update forward edges + for (const dep of dependencies) { + if (!this.forwardEdges.has(dep)) { + this.forwardEdges.set(dep, new Set()); + } + this.forwardEdges.get(dep)!.add(hash); + this.frontier.delete(dep); + } - const vertex = new Vertex(hash, operation, new Set(dependencies)); - this.vertices.set(hash, vertex); - this.frontier.add(hash); + return hash; + } - // update root - if (dependencies.length === 0) { - this.root = hash; - } - // Update forward edges - for (const dep of dependencies) { - if (!this.forwardEdges.has(dep)) { - this.forwardEdges.set(dep, new Set()); - } - this.forwardEdges.get(dep)!.add(hash); - this.frontier.delete(dep); - } + // Time complexity: O(V + E), Space complexity: O(V) + topologicalSort(): Hash[] { + const result: Hash[] = []; + const visited = new Set(); - return hash; - } - - // Time complexity: O(V + E), Space complexity: O(V) - topologicalSort(): Hash[] { - const result: Hash[] = []; - const visited = new Set(); - - const visit = (hash: Hash) => { - if (visited.has(hash)) return; - - const vertex = this.vertices.get(hash)!; - visited.add(hash); - result.push(hash); - - // Check if all dependencies are visited - if (Array.from(vertex.dependencies).every(dep => visited.has(dep))) { - const children = this.forwardEdges.get(hash) || new Set(); - for (const child of children) { - // console.log("Visiting child: ", child); - visit(child); - } - } - }; - // Start with the root vertex - visit(this.root); - - return result; + const visit = (hash: Hash) => { + if (visited.has(hash)) return; + + const vertex = this.vertices.get(hash)!; + visited.add(hash); + result.push(hash); + + // Check if all dependencies are visited + if (Array.from(vertex.dependencies).every(dep => visited.has(dep))) { + const children = this.forwardEdges.get(hash) || new Set(); + for (const child of children) { + // console.log("Visiting child: ", child); + visit(child); } + } + }; + // Start with the root vertex + visit(this.root); - linearizeOps(): T[] { - const order = this.topologicalSort(); - const result: T[] = []; - let i = 0; - - while (i < order.length) { - const anchor = order[i]; - let j = i + 1; - let shouldIncrementI = true; - - while (j < order.length) { - const moving = order[j]; - - if (!this.areCausallyRelated(anchor, moving)) { - const op1 = this.vertices.get(anchor)!.operation; - const op2 = this.vertices.get(moving)!.operation; - const action = this.resolveConflicts(op1, op2); - - switch (action) { - case ActionType.DropLeft: - order.splice(i, 1); - j = order.length; // Break out of inner loop - shouldIncrementI = false; - continue; // Continue outer loop without incrementing i - case ActionType.DropRight: - order.splice(j, 1); - continue; // Continue with the same j - case ActionType.Swap: - [order[i], order[j]] = [order[j], order[i]]; - j = order.length; // Break out of inner loop - break; - case ActionType.Nop: - j++; - break; - } - } else { - j++; - } - } - - if (shouldIncrementI) { - result.push(this.vertices.get(order[i])!.operation); - i++; - } + return result; + } + + linearizeOps(): T[] { + const order = this.topologicalSort(); + const result: T[] = []; + let i = 0; + + while (i < order.length) { + const anchor = order[i]; + let j = i + 1; + let shouldIncrementI = true; + + while (j < order.length) { + const moving = order[j]; + + if (!this.areCausallyRelated(anchor, moving)) { + const op1 = this.vertices.get(anchor)!.operation; + const op2 = this.vertices.get(moving)!.operation; + const action = this.resolveConflicts(op1, op2); + + switch (action) { + case ActionType.DropLeft: + order.splice(i, 1); + j = order.length; // Break out of inner loop + shouldIncrementI = false; + continue; // Continue outer loop without incrementing i + case ActionType.DropRight: + order.splice(j, 1); + continue; // Continue with the same j + case ActionType.Swap: + [order[i], order[j]] = [order[j], order[i]]; + j = order.length; // Break out of inner loop + break; + case ActionType.Nop: + j++; + break; } - - return result; + } else { + j++; + } + } + + if (shouldIncrementI) { + result.push(this.vertices.get(order[i])!.operation); + i++; } + } + + return result; + } - // Time complexity: O(V), Space complexity: O(V) - private areCausallyRelated(hash1: Hash, hash2: Hash): boolean { - const visited = new Set(); - const stack = [hash1]; - - while (stack.length > 0) { - const current = stack.pop()!; - if (current === hash2) return true; - visited.add(current); - - const vertex = this.vertices.get(current)!; - for (const dep of vertex.dependencies) { - if (!visited.has(dep)) { - stack.push(dep); - } - } + // Time complexity: O(V), Space complexity: O(V) + private areCausallyRelated(hash1: Hash, hash2: Hash): boolean { + const visited = new Set(); + const stack = [hash1]; + + while (stack.length > 0) { + const current = stack.pop()!; + if (current === hash2) return true; + visited.add(current); + + const vertex = this.vertices.get(current)!; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); } + } + } - visited.clear(); - stack.push(hash2); + visited.clear(); + stack.push(hash2); - while (stack.length > 0) { - const current = stack.pop()!; - if (current === hash1) return true; - visited.add(current); + while (stack.length > 0) { + const current = stack.pop()!; + if (current === hash1) return true; + visited.add(current); - const vertex = this.vertices.get(current)!; - for (const dep of vertex.dependencies) { - if (!visited.has(dep)) { - stack.push(dep); - } - } + const vertex = this.vertices.get(current)!; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); } - - return false; + } } - // Time complexity: O(1), Space complexity: O(1) - getFrontier(): Hash[] { - return Array.from(this.frontier); + return false; + } + + // Time complexity: O(1), Space complexity: O(1) + getFrontier(): Hash[] { + return Array.from(this.frontier); } // Time complexity: O(1), Space complexity: O(1) getRoots(): Hash { - return this.root; + return this.root; } // Time complexity: O(1), Space complexity: O(1) getDependencies(hash: Hash): Hash[] { - return Array.from(this.vertices.get(hash)?.dependencies || []); + return Array.from(this.vertices.get(hash)?.dependencies || []); } // Time complexity: O(1), Space complexity: O(1) getVertex(hash: Hash): Vertex | undefined { - return this.vertices.get(hash); + return this.vertices.get(hash); } // Time complexity: O(V), Space complexity: O(V) getAllVertices(): Vertex[] { - return Array.from(this.vertices.values()); + return Array.from(this.vertices.values()); } } \ No newline at end of file