Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #116 from mgechev/segment-tree
feat(DataStructures): add segment tree
- Loading branch information
Showing
2 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** | ||
* Implementation of a segment tree. | ||
* | ||
* @example | ||
* var SegmentTree = require('path-to-algorithms/src/data-structures'+ | ||
* '/segment-tree').SegmentTree; | ||
* | ||
* var tree = SegmentTree.indexArray([-1, 2, 4, 0], Infinity, function (a, b) { | ||
* return Math.min(a, b); | ||
* }); | ||
* | ||
* @public | ||
* @constructor | ||
* @param {any} placeholder A placeholder value dpendent on the aggregate. | ||
* @param {Function} aggregate Generates the values for the intermediate nodes. | ||
* @module data-structures/segment-tree | ||
*/ | ||
(function (exports) { | ||
|
||
'use strict'; | ||
|
||
/** | ||
* SegmentTree constructor. | ||
* | ||
* @public | ||
* @constructor | ||
* @param {any} invalidValue Invalid value to be returned depending | ||
* on the aggregate. | ||
* @param {Function} aggregate Function to generate the intermediate | ||
* values in the tree. | ||
*/ | ||
function SegmentTree(invalidValue, aggregate) { | ||
this._data = []; | ||
this._original = null; | ||
this._invalidValue = invalidValue; | ||
this._aggregate = aggregate; | ||
} | ||
|
||
/** | ||
* Creates a segment tree using an array passed as element. | ||
* | ||
* @static | ||
* @public | ||
* @param {Array} array Array to be indexed. | ||
* @param {Function} aggregate Function used for generation of | ||
* intermediate nodes. | ||
*/ | ||
SegmentTree.indexArray = function (array, placeholder, aggregate) { | ||
var segmentize = function (original, data, lo, hi, idx) { | ||
if (lo === hi) { | ||
data[idx] = original[lo]; | ||
} else { | ||
var mid = Math.floor((lo + hi) / 2); | ||
var left = 2 * idx + 1; | ||
var right = 2 * idx + 2; | ||
segmentize(original, data, lo, mid, left); | ||
segmentize(original, data, mid + 1, hi, right); | ||
data[idx] = aggregate(data[left], data[right]); | ||
} | ||
}; | ||
var result = []; | ||
if (array && array.length) { | ||
segmentize(array, result, 0, array.length - 1, 0); | ||
} | ||
var tree = new SegmentTree(placeholder, aggregate); | ||
tree._data = result; | ||
tree._original = array; | ||
return tree; | ||
}; | ||
|
||
/** | ||
* Queries the SegmentTree in given range based on the set aggregate. | ||
* | ||
* @param {Number} start The start index of the interval. | ||
* @param {Number} end The end index of the interval. | ||
*/ | ||
SegmentTree.prototype.query = function (start, end) { | ||
if (start > end) { | ||
throw new Error('The start index should be smaller by the end index'); | ||
} | ||
var findEl = function (originalArrayStart, originalArrayEnd, current) { | ||
if (start > originalArrayEnd) { | ||
return this._invalidValue; | ||
} | ||
if (end < originalArrayStart) { | ||
return this._invalidValue; | ||
} | ||
if (start === originalArrayStart && end === originalArrayEnd || | ||
originalArrayStart === originalArrayEnd) { | ||
return this._data[current]; | ||
} | ||
var originalArrayMid = | ||
Math.floor((originalArrayStart + originalArrayEnd) / 2); | ||
return this._aggregate( | ||
findEl(originalArrayStart, originalArrayMid, 2 * current + 1), | ||
findEl(originalArrayMid + 1, originalArrayEnd, 2 * current + 2) | ||
); | ||
}.bind(this); | ||
return findEl(0, this._original.length - 1, 0, this._aggregate); | ||
}; | ||
|
||
exports.SegmentTree = SegmentTree; | ||
|
||
}(typeof window === 'undefined' ? module.exports : window)); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
'use strict'; | ||
|
||
var SegmentTree = require('../../src/data-structures/segment-tree.js') | ||
.SegmentTree; | ||
|
||
var defaultAggregate = function (a, b) { | ||
return Math.min(a, b); | ||
}; | ||
|
||
describe('Segment Tree', function () { | ||
|
||
describe('indexing', function () { | ||
|
||
it('should be a constructor function', function () { | ||
expect(typeof SegmentTree).toBe('function'); | ||
}); | ||
|
||
it('should start with null original array', function () { | ||
expect(new SegmentTree()._original).toBe(null); | ||
}); | ||
|
||
it('should start with empty array as data', function () { | ||
expect(new SegmentTree()._data).not.toBe(null); | ||
expect(new SegmentTree()._data.length).toBe(0); | ||
}); | ||
|
||
it('should work with empty arrays', function () { | ||
var tree = SegmentTree.indexArray([], Infinity, defaultAggregate); | ||
expect(tree._data).toBeTruthy(); | ||
expect(tree._data.length).toBe(0); | ||
}); | ||
|
||
it('should index arrays with one element', function () { | ||
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate); | ||
expect(tree._data).toBeTruthy(); | ||
expect(tree._data.length).toBe(1); | ||
}); | ||
|
||
it('should index any array', function () { | ||
var tree = SegmentTree.indexArray([1, 2, 3], Infinity, defaultAggregate); | ||
expect(tree._data).toEqual([1, 1, 3, 1, 2]); | ||
|
||
tree = SegmentTree.indexArray([1, 2, 3, 6], Infinity, defaultAggregate); | ||
expect(tree._data).toEqual([1, 1, 3, 1, 2, 3, 6]); | ||
}); | ||
|
||
}); | ||
|
||
describe('should find the proper value at given interval', function () { | ||
|
||
it('should properly find the minimum when in range', function () { | ||
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate); | ||
expect(tree.query(0, 0)).toBe(1); | ||
|
||
tree = SegmentTree.indexArray([1, 2], Infinity, defaultAggregate); | ||
expect(tree.query(0, 0)).toBe(1); | ||
expect(tree.query(0, 1)).toBe(1); | ||
expect(tree.query(1, 1)).toBe(2); | ||
|
||
tree = SegmentTree.indexArray([1, -1, 2], Infinity, defaultAggregate); | ||
expect(tree.query(0, 2)).toBe(-1); | ||
expect(tree.query(0, 1)).toBe(-1); | ||
expect(tree.query(1, 1)).toBe(-1); | ||
expect(tree.query(1, 2)).toBe(-1); | ||
expect(tree.query(2, 2)).toBe(2); | ||
}); | ||
|
||
it('should properly find the minimum when outside range', function () { | ||
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate); | ||
expect(tree.query(0, 2)).toBe(1); | ||
|
||
tree = SegmentTree.indexArray([1, 2, 3], Infinity, defaultAggregate); | ||
expect(tree.query(0, 20)).toBe(1); | ||
expect(tree.query(2, 20)).toBe(3); | ||
expect(Number.isFinite(tree.query(20, 25))).toBe(false); | ||
}); | ||
|
||
it('should throw when the start index is bigger than end', function () { | ||
var tree = SegmentTree.indexArray([1], Infinity, defaultAggregate); | ||
expect(function () { | ||
tree.query(2, 1); | ||
}).toThrow(); | ||
expect(function () { | ||
tree.query(1, 1); | ||
}).not.toThrow(); | ||
}); | ||
}); | ||
}); | ||
|