Skip to content

Commit 34217ff

Browse files
committed
feat(DataStructures): add segment tree
1 parent eba273b commit 34217ff

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Implementation of a segment tree.
3+
*
4+
* @example
5+
* var SegmentTree = require('path-to-algorithms/src/data-structures'+
6+
* '/segment-tree').SegmentTree;
7+
*
8+
* var tree = SegmentTree.indexArray([-1, 2, 4, 0], Infinity, function (a, b) {
9+
* return Math.min(a, b);
10+
* });
11+
*
12+
* @public
13+
* @constructor
14+
* @param {any} placeholder A placeholder value dpendent on the aggregate.
15+
* @param {Function} aggregate Generates the values for the intermediate nodes.
16+
* @module data-structures/segment-tree
17+
*/
18+
(function (exports) {
19+
20+
'use strict';
21+
22+
/**
23+
* SegmentTree constructor.
24+
*
25+
* @public
26+
* @constructor
27+
* @param {any} invalidValue Invalid value to be returned depending
28+
* on the aggregate.
29+
* @param {Function} aggregate Function to generate the intermediate
30+
* values in the tree.
31+
*/
32+
function SegmentTree(invalidValue, aggregate) {
33+
this._data = [];
34+
this._original = null;
35+
this._invalidValue = invalidValue;
36+
this._aggregate = aggregate;
37+
}
38+
39+
/**
40+
* Creates a segment tree using an array passed as element.
41+
*
42+
* @static
43+
* @public
44+
* @param {Array} array Array to be indexed.
45+
* @param {Function} aggregate Function used for generation of
46+
* intermediate nodes.
47+
*/
48+
SegmentTree.indexArray = function (array, placeholder, aggregate) {
49+
var segmentize = function (original, data, lo, hi, idx) {
50+
if (lo === hi) {
51+
data[idx] = original[lo];
52+
} else {
53+
var mid = Math.floor((lo + hi) / 2);
54+
var left = 2 * idx + 1;
55+
var right = 2 * idx + 2;
56+
segmentize(original, data, lo, mid, left);
57+
segmentize(original, data, mid + 1, hi, right);
58+
data[idx] = aggregate(data[left], data[right]);
59+
}
60+
};
61+
var result = [];
62+
if (array && array.length) {
63+
segmentize(array, result, 0, array.length - 1, 0);
64+
}
65+
var tree = new SegmentTree(placeholder, aggregate);
66+
tree._data = result;
67+
tree._original = array;
68+
return tree;
69+
};
70+
71+
/**
72+
* Queries the SegmentTree in given range based on the set aggregate.
73+
*
74+
* @param {Number} start The start index of the interval.
75+
* @param {Number} end The end index of the interval.
76+
*/
77+
SegmentTree.prototype.query = function (start, end) {
78+
if (start > end) {
79+
throw new Error('The start index should be smaller by the end index');
80+
}
81+
var findEl = function (originalArrayStart, originalArrayEnd, current) {
82+
if (start > originalArrayEnd) {
83+
return this._invalidValue;
84+
}
85+
if (end < originalArrayStart) {
86+
return this._invalidValue;
87+
}
88+
if (start === originalArrayStart && end === originalArrayEnd ||
89+
originalArrayStart === originalArrayEnd) {
90+
return this._data[current];
91+
}
92+
var originalArrayMid =
93+
Math.floor((originalArrayStart + originalArrayEnd) / 2);
94+
return this._aggregate(
95+
findEl(originalArrayStart, originalArrayMid, 2 * current + 1),
96+
findEl(originalArrayMid + 1, originalArrayEnd, 2 * current + 2)
97+
);
98+
}.bind(this);
99+
return findEl(0, this._original.length - 1, 0, this._aggregate);
100+
};
101+
102+
exports.SegmentTree = SegmentTree;
103+
104+
}(typeof window === 'undefined' ? module.exports : window));
105+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
3+
var SegmentTree = require('../../src/data-structures/segment-tree.js')
4+
.SegmentTree;
5+
6+
var defaultAggregate = function (a, b) {
7+
return Math.min(a, b);
8+
};
9+
10+
describe('Segment Tree', function () {
11+
12+
describe('indexing', function () {
13+
14+
it('should be a constructor function', function () {
15+
expect(typeof SegmentTree).toBe('function');
16+
});
17+
18+
it('should start with null original array', function () {
19+
expect(new SegmentTree()._original).toBe(null);
20+
});
21+
22+
it('should start with empty array as data', function () {
23+
expect(new SegmentTree()._data).not.toBe(null);
24+
expect(new SegmentTree()._data.length).toBe(0);
25+
});
26+
27+
it('should work with empty arrays', function () {
28+
var tree = SegmentTree.indexArray([], Infinity, defaultAggregate);
29+
expect(tree._data).toBeTruthy();
30+
expect(tree._data.length).toBe(0);
31+
});
32+
33+
it('should index arrays with one element', function () {
34+
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate);
35+
expect(tree._data).toBeTruthy();
36+
expect(tree._data.length).toBe(1);
37+
});
38+
39+
it('should index any array', function () {
40+
var tree = SegmentTree.indexArray([1, 2, 3], Infinity, defaultAggregate);
41+
expect(tree._data).toEqual([1, 1, 3, 1, 2]);
42+
43+
tree = SegmentTree.indexArray([1, 2, 3, 6], Infinity, defaultAggregate);
44+
expect(tree._data).toEqual([1, 1, 3, 1, 2, 3, 6]);
45+
});
46+
47+
});
48+
49+
describe('should find the proper value at given interval', function () {
50+
51+
it('should properly find the minimum when in range', function () {
52+
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate);
53+
expect(tree.query(0, 0)).toBe(1);
54+
55+
tree = SegmentTree.indexArray([1, 2], Infinity, defaultAggregate);
56+
expect(tree.query(0, 0)).toBe(1);
57+
expect(tree.query(0, 1)).toBe(1);
58+
expect(tree.query(1, 1)).toBe(2);
59+
60+
tree = SegmentTree.indexArray([1, -1, 2], Infinity, defaultAggregate);
61+
expect(tree.query(0, 2)).toBe(-1);
62+
expect(tree.query(0, 1)).toBe(-1);
63+
expect(tree.query(1, 1)).toBe(-1);
64+
expect(tree.query(1, 2)).toBe(-1);
65+
expect(tree.query(2, 2)).toBe(2);
66+
});
67+
68+
it('should properly find the minimum when outside range', function () {
69+
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate);
70+
expect(tree.query(0, 2)).toBe(1);
71+
72+
tree = SegmentTree.indexArray([1, 2, 3], Infinity, defaultAggregate);
73+
expect(tree.query(0, 20)).toBe(1);
74+
expect(tree.query(2, 20)).toBe(3);
75+
expect(Number.isFinite(tree.query(20, 25))).toBe(false);
76+
});
77+
78+
it('should throw when the start index is bigger than end', function () {
79+
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate);
80+
expect(function () {
81+
tree.query(2, 1);
82+
}).toThrow();
83+
expect(function () {
84+
tree.query(1, 1);
85+
}).not.toThrow();
86+
});
87+
});
88+
});
89+

0 commit comments

Comments
 (0)