Skip to content

Commit 8a4f117

Browse files
pschikAdministrator
and
Administrator
authored
merge: add UnionFind.js plus tests (#814)
* Create UnionFind.js * Create UnionFindTest.js * add UnionFind.js plus tests * implement PR comments * implement PR comment * fix codestyle Co-authored-by: Administrator <[email protected]>
1 parent 4fa3c5e commit 8a4f117

File tree

4 files changed

+185
-10943
lines changed

4 files changed

+185
-10943
lines changed

Search/UnionFind.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* union find data structure for javascript
3+
*
4+
* In computer science, a disjoint-set data structure, also called a union–find data structure or merge–find set,
5+
* is a data structure that stores a collection of disjoint (non-overlapping) sets. Equivalently, it stores a partition
6+
* of a set into disjoint subsets. It provides operations for adding new sets, merging sets (replacing them by their union),
7+
* and finding a representative member of a set.
8+
* The last operation allows to find out efficiently if any two elements are in the same or different sets.
9+
*
10+
* Disjoint-set data structures play a key role in Kruskal's algorithm for finding the minimum spanning tree of a graph.
11+
* The importance of minimum spanning trees means that disjoint-set data structures underlie a wide variety of algorithms.
12+
* In addition, disjoint-set data structures also have applications to symbolic computation, as well in compilers,
13+
* especially for register allocation problems.
14+
*
15+
* you can learn more on disjoint-set / union–find data structure at https://en.wikipedia.org/wiki/Disjoint-set_data_structure
16+
*/
17+
function UnionFind (n, key) {
18+
if (!(this instanceof UnionFind)) return new UnionFind(n)
19+
if (key && typeof key !== 'function') {
20+
throw new Error('key has to be a function or else left undefined')
21+
}
22+
let cnt, length
23+
// init Union Find with number of distinct groups. Each group will be referred to as index of the array of size 'size' starting at 0.
24+
// Provide an optional key function that maps these indices. I.e. for the groups starting with 1 provide function(a){return a-1;}. The default value is function(a){return a;}.
25+
key = key || function (a) { return a }
26+
cnt = length = n
27+
const id = new Array(n)
28+
const sz = new Array(n)
29+
for (let i = 0; i < n; i++) {
30+
id[i] = i
31+
sz[i] = 1
32+
}
33+
// Returns the number of elements of uf object.
34+
this.size = function () {
35+
return length
36+
}
37+
// Returns the number of distinct groups left inside the object.
38+
this.count = function () {
39+
return cnt
40+
}
41+
// Return the root (value) of the group in which p is.
42+
this.find = function (p) {
43+
p = key(p)
44+
while (p !== id[p]) {
45+
id[p] = id[id[p]]
46+
p = id[p]
47+
}
48+
return p
49+
}
50+
// Returns true if p and p are both in same group, false otherwise.
51+
this.connected = function (p, q) {
52+
p = key(p)
53+
q = key(q)
54+
ensureIndexWithinBounds(p, q)
55+
return this.find(p) === this.find(q)
56+
}
57+
// Combine elements in groups p and q into a single group. In other words connect the two groups.
58+
this.union = function (p, q) {
59+
p = key(p)
60+
q = key(q)
61+
ensureIndexWithinBounds(p, q)
62+
const i = this.find(p)
63+
const j = this.find(q)
64+
if (i === j) return
65+
if (sz[i] < sz[j]) {
66+
id[i] = j; sz[j] += sz[i]
67+
} else {
68+
id[j] = i; sz[i] += sz[j]
69+
}
70+
cnt--
71+
}
72+
function ensureIndexWithinBounds (args) {
73+
for (let i = arguments.length - 1; i >= 0; i--) {
74+
const p = arguments[i]
75+
if (p >= length) throw new Error('Index out of bounds. The maximum index can be length-1')
76+
}
77+
}
78+
}
79+
80+
export { UnionFind }

Search/test/UnionFind.test.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { UnionFind } from '../UnionFind'
2+
3+
const uf = new UnionFind(5)
4+
5+
test('should expose .size():', () => {
6+
const size = uf.size()
7+
expect(size).toBe(5)
8+
})
9+
10+
test('should do .union(num1, num2):', () => {
11+
uf.union(1, 2)
12+
uf.union(3, 4)
13+
uf.union(0, 4)
14+
expect(uf.connected(1, 2)).toBe(true)
15+
expect(uf.connected(1, 2)).toBe(true)
16+
17+
expect(uf.connected(3, 4)).toBe(true)
18+
expect(uf.connected(3, 0)).toBe(true)
19+
expect(uf.connected(4, 0)).toBe(true)
20+
21+
expect(uf.connected(1, 3)).toBe(false)
22+
expect(uf.connected(1, 4)).toBe(false)
23+
expect(uf.connected(1, 0)).toBe(false)
24+
expect(uf.connected(2, 3)).toBe(false)
25+
expect(uf.connected(2, 4)).toBe(false)
26+
expect(uf.connected(2, 0)).toBe(false)
27+
})
28+
29+
test('.count(), should return the number of disparate groups:', () => {
30+
expect(uf.count()).toBe(2)
31+
})
32+
33+
test('should check if two components are connected, .connected(num1, num2):', () => {
34+
expect(uf.connected(1, 2)).toBe(true)
35+
expect(uf.connected(1, 3)).toBe(false)
36+
})
37+
38+
test('should find the root of the tree in which the given element lives, .find(num):', () => {
39+
expect(uf.find(1)).toBe(1)
40+
expect(uf.find(2)).toBe(1)
41+
expect(uf.find(3)).toBe(3)
42+
expect(uf.find(4)).toBe(3)
43+
expect(uf.find(0)).toBe(3)
44+
})
45+
46+
test('should always change the id of the smaller tree and preserve the id of the larger one', () => {
47+
uf.union(2, 3)
48+
expect(uf.count()).toBe(1)
49+
expect(uf.find(0)).toBe(3)
50+
expect(uf.find(1)).toBe(3)
51+
expect(uf.find(2)).toBe(3)
52+
expect(uf.find(3)).toBe(3)
53+
expect(uf.find(4)).toBe(3)
54+
})

0 commit comments

Comments
 (0)