Skip to content

Commit b1178be

Browse files
committed
feat: disjoint-set
1 parent 6f16f27 commit b1178be

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { InternalError } from '@rushstack/node-core-library';
5+
6+
/**
7+
* A disjoint set data structure
8+
*/
9+
export class DisjointSet<T extends object> {
10+
private _forest: WeakSet<T>;
11+
private _parentMap: WeakMap<T, T>;
12+
private _sizeMap: WeakMap<T, number>;
13+
14+
public constructor() {
15+
this._forest = new WeakSet<T>();
16+
this._parentMap = new WeakMap<T, T>();
17+
this._sizeMap = new WeakMap<T, number>();
18+
}
19+
20+
/**
21+
* Adds a new set containing specific object
22+
*/
23+
public add(x: T): void {
24+
if (this._forest.has(x)) {
25+
return;
26+
}
27+
28+
this._forest.add(x);
29+
this._parentMap.set(x, x);
30+
this._sizeMap.set(x, 1);
31+
}
32+
33+
/**
34+
* Unions the sets that contain two objects
35+
*/
36+
public union(a: T, b: T): void {
37+
let x: T = this._find(a);
38+
let y: T = this._find(b);
39+
40+
if (x === y) {
41+
// x and y are already in the same set
42+
return;
43+
}
44+
45+
if (this._getSize(x) < this._getSize(y)) {
46+
const t: T = x;
47+
x = y;
48+
y = t;
49+
}
50+
this._parentMap.set(y, x);
51+
this._sizeMap.set(x, this._getSize(x) + this._getSize(y));
52+
}
53+
54+
/**
55+
* Returns true if x and y are in the same set
56+
*/
57+
public isConnected(x: T, y: T): boolean {
58+
return this._find(x) === this._find(y);
59+
}
60+
61+
private _find(a: T): T {
62+
let x: T = a;
63+
while (this._getParent(x) !== x) {
64+
this._parentMap.set(x, this._getParent(this._getParent(x)));
65+
x = this._getParent(x);
66+
}
67+
return x;
68+
}
69+
70+
private _getParent(x: T): T {
71+
const parent: T | undefined = this._parentMap.get(x);
72+
if (parent === undefined) {
73+
// This should not happen
74+
throw new InternalError(`Can not find parent`);
75+
}
76+
return parent;
77+
}
78+
79+
private _getSize(x: T): number {
80+
const size: number | undefined = this._sizeMap.get(x);
81+
if (size === undefined) {
82+
// This should not happen
83+
throw new InternalError(`Can not get size`);
84+
}
85+
return size;
86+
}
87+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { DisjointSet } from '../DisjointSet';
2+
3+
describe(DisjointSet.name, () => {
4+
it('can disjoint two sets', () => {
5+
const disjointSet = new DisjointSet<{ id: number }>();
6+
const obj1 = { id: 1 };
7+
const obj2 = { id: 2 };
8+
disjointSet.add(obj1);
9+
disjointSet.add(obj2);
10+
11+
expect(disjointSet.isConnected(obj1, obj2)).toBe(false);
12+
});
13+
14+
it('can disjoint multiple sets', () => {
15+
const disjointSet = new DisjointSet<{ id: number }>();
16+
const obj1 = { id: 1 };
17+
const obj2 = { id: 2 };
18+
const obj3 = { id: 3 };
19+
const obj4 = { id: 4 };
20+
disjointSet.add(obj1);
21+
disjointSet.add(obj2);
22+
disjointSet.add(obj3);
23+
disjointSet.add(obj4);
24+
25+
expect(disjointSet.isConnected(obj1, obj2)).toBe(false);
26+
expect(disjointSet.isConnected(obj1, obj3)).toBe(false);
27+
expect(disjointSet.isConnected(obj1, obj4)).toBe(false);
28+
});
29+
30+
it('can union two sets', () => {
31+
const disjointSet = new DisjointSet<{ id: number }>();
32+
const obj1 = { id: 1 };
33+
const obj2 = { id: 2 };
34+
disjointSet.add(obj1);
35+
disjointSet.add(obj2);
36+
expect(disjointSet.isConnected(obj1, obj2)).toBe(false);
37+
38+
disjointSet.union(obj1, obj2);
39+
expect(disjointSet.isConnected(obj1, obj2)).toBe(true);
40+
});
41+
42+
it('can union two sets transitively', () => {
43+
const disjointSet = new DisjointSet<{ id: number }>();
44+
const obj1 = { id: 1 };
45+
const obj2 = { id: 2 };
46+
const obj3 = { id: 3 };
47+
disjointSet.add(obj1);
48+
disjointSet.add(obj2);
49+
disjointSet.add(obj3);
50+
51+
disjointSet.union(obj1, obj2);
52+
expect(disjointSet.isConnected(obj1, obj2)).toBe(true);
53+
expect(disjointSet.isConnected(obj1, obj3)).toBe(false);
54+
expect(disjointSet.isConnected(obj2, obj3)).toBe(false);
55+
56+
disjointSet.union(obj1, obj3);
57+
expect(disjointSet.isConnected(obj1, obj2)).toBe(true);
58+
expect(disjointSet.isConnected(obj2, obj3)).toBe(true);
59+
expect(disjointSet.isConnected(obj1, obj3)).toBe(true);
60+
});
61+
62+
it('can union and disjoint sets', () => {
63+
const disjointSet = new DisjointSet<{ id: number }>();
64+
const obj1 = { id: 1 };
65+
const obj2 = { id: 2 };
66+
const obj3 = { id: 3 };
67+
const obj4 = { id: 4 };
68+
disjointSet.add(obj1);
69+
disjointSet.add(obj2);
70+
disjointSet.add(obj3);
71+
disjointSet.add(obj4);
72+
73+
expect(disjointSet.isConnected(obj1, obj2)).toBe(false);
74+
expect(disjointSet.isConnected(obj1, obj3)).toBe(false);
75+
expect(disjointSet.isConnected(obj1, obj4)).toBe(false);
76+
77+
disjointSet.union(obj1, obj2);
78+
expect(disjointSet.isConnected(obj1, obj2)).toBe(true);
79+
expect(disjointSet.isConnected(obj1, obj3)).toBe(false);
80+
expect(disjointSet.isConnected(obj1, obj4)).toBe(false);
81+
});
82+
});

0 commit comments

Comments
 (0)