Skip to content

Commit

Permalink
Merge pull request #116 from mgechev/segment-tree
Browse files Browse the repository at this point in the history
feat(DataStructures): add segment tree
  • Loading branch information
mgechev committed Mar 23, 2017
2 parents c4ace61 + 34217ff commit dd8258a
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/data-structures/segment-tree.js
@@ -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));

89 changes: 89 additions & 0 deletions test/data-structures/segment-tree.spec.js
@@ -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();
});
});
});

0 comments on commit dd8258a

Please sign in to comment.