/** * Implements a binary indexed tree, aka Fenwick tree. See * http://en.wikipedia.org/wiki/Fenwick_tree */ function BinaryIndexedTree(optSize) { this.tree = []; if (optSize > 0) { this.tree.length = optSize; for (var i = 0; i < optSize; i++) { this.tree[i] = 0; } // The last valid index rounded down to the nearest power of 2. this.mask = mostSignificantOne(this.tree.length - 1); } } /** * Returns a number that contains only the least significant one in `num`. * @param {Number} num - Positive integer. * @returns {Number} The least significant one in `num`, e.g. for 10110, returns 00010. */ function leastSignificantOne(num) { return num & (-num); } BinaryIndexedTree.leastSignificantOne = leastSignificantOne; /** * Strips the least significant one from `num`. * @param {Number} num - Positive integer. * @returns {Number} `num` with the least significant one removed, e.g. for 10110, returns 10100. */ function stripLeastSignificantOne(num) { return num & (num - 1); } BinaryIndexedTree.stripLeastSignificantOne = stripLeastSignificantOne; function mostSignificantOne(num) { if (num === 0) { return 0; } var msb = 1; while ((num >>>= 1)) { msb <<= 1; } return msb; } BinaryIndexedTree.mostSignificantOne = mostSignificantOne; /** * Converts in-place an array of cumulative values to the original values. * @param {Array[Number]} values - Array of cumulative values, or partial sums. * @returns {Array[Number]} - same `values` array, with elements replaced by deltas. * E.g. [1,3,6,10] is converted to [1,2,3,4]. */ function cumulToValues(values) { for (var i = values.length - 1; i >= 1; i--) { values[i] -= values[i - 1]; } return values; } BinaryIndexedTree.cumulToValues = cumulToValues; /** * Converts in-place an array of values to cumulative values, or partial sums. * @param {Array[Number]} values - Array of numerical values. * @returns {Array[Number]} - same `values` array, with elements replaced by partial sums. * E.g. [1,2,3,4] is converted to [1,3,6,10]. */ function valuesToCumul(values) { for (var i = 1; i < values.length; i++) { values[i] += values[i - 1]; } return values; } BinaryIndexedTree.valuesToCumul = valuesToCumul; /** * @returns {Number} length of the tree. */ BinaryIndexedTree.prototype.size = function() { return this.tree.length; }; /** * Converts the BinaryIndexedTree to a cumulative array. * Takes time linear in the size of the array. * @returns {Array[Number]} - array with each element a partial sum. */ BinaryIndexedTree.prototype.toCumulativeArray = function() { var cumulValues = [this.tree[0]]; var len = cumulValues.length = this.tree.length; for (var i = 1; i < len; i++) { cumulValues[i] = this.tree[i] + cumulValues[stripLeastSignificantOne(i)]; } return cumulValues; }; /** * Converts the BinaryIndexedTree to an array of individual values. * Takes time linear in the size of the array. * @returns {Array[Number]} - array with each element containing the value that was inserted. */ BinaryIndexedTree.prototype.toValueArray = function() { return cumulToValues(this.toCumulativeArray()); }; /** * Creates a tree from an array of cumulative values. * Takes time linear in the size of the array. * @param {Array[Number]} - array with each element a partial sum. */ BinaryIndexedTree.prototype.fillFromCumulative = function(cumulValues) { var len = this.tree.length = cumulValues.length; if (len > 0) { this.tree[0] = cumulValues[0]; for (var i = 1; i < len; i++) { this.tree[i] = cumulValues[i] - cumulValues[stripLeastSignificantOne(i)]; } // The last valid index rounded down to the nearest power of 2. this.mask = mostSignificantOne(this.tree.length - 1); } else { this.mask = 0; } }; /** * Creates a tree from an array of invididual values. * Takes time linear in the size of the array. * @param {Array[Number]} - array with each element containing the value to insert. */ BinaryIndexedTree.prototype.fillFromValues = function(values) { this.fillFromCumulative(valuesToCumul(values.slice())); }; /** * Reads the cumulative value at the given index. Takes time O(log(index)). * @param {Number} index - index in the array. * @returns {Number} - cumulative values up to and including `index`. */ BinaryIndexedTree.prototype.getCumulativeValue = function(index) { var sum = this.tree[0]; while (index > 0) { sum += this.tree[index]; index = stripLeastSignificantOne(index); } return sum; }; /** * Reads the cumulative value from start(inclusive) to end(exclusive). Takes time O(log(end)). * @param {Number} start - start index * @param {Number} end - end index * @returns {Number} - cumulative values between start(inclusive) and end(exclusive) */ BinaryIndexedTree.prototype.getCumulativeValueRange = function(start, end) { return this.getSumTo(end) - this.getSumTo(start); }; /** * Returns the sum of values up to the given index. Takes time O(log(index)). * @param {Number} index - index in the array. * @returns {Number} - cumulative values up to but not including `index`. */ BinaryIndexedTree.prototype.getSumTo = function(index) { return (index > 0 ? this.getCumulativeValue(index - 1) : 0); }; /** * Returns the total of all values in the tree. Takes time O(log(N)). * @returns {Number} - sum of all values. */ BinaryIndexedTree.prototype.getTotal = function() { return this.getCumulativeValue(this.tree.length - 1); }; /** * Reads a single value at the given index. Takes time O(log(index)). * @param {Number} index - index in the array. * @returns {Number} - the value that was inserted at `index`. */ BinaryIndexedTree.prototype.getValue = function(index) { var value = this.tree[index]; if (index > 0) { var parent = stripLeastSignificantOne(index); index--; while (index !== parent) { value -= this.tree[index]; index = stripLeastSignificantOne(index); } } return value; }; /** * Updates a value at an index. Takes time O(log(table size)). * @param {Number} index - index in the array. * @param {Number} delta - value to add to the previous value at `index`. */ BinaryIndexedTree.prototype.addValue = function(index, delta) { if (index === 0) { this.tree[0] += delta; } else { while (index < this.tree.length) { this.tree[index] += delta; index += leastSignificantOne(index); } } }; /** * Sets a value at an index. Takes time O(log(table size)). * @param {Number} index - index in the array. * @param {Number} value - new value to set at `index`. */ BinaryIndexedTree.prototype.setValue = function(index, value) { this.addValue(index, value - this.getValue(index)); }; /** * Given a cumulative value, finds the first element whose inclusion reaches the value. * E.g. for values [1,2,3,4] (cumulative [1,3,6,10]), getIndex(3) = 1, getIndex(3.1) = 2. * @param {Number} cumulValue - cumulative value to exceed. * @returns {Number} index - the first index such that getCumulativeValue(index) >= cumulValue. * If cumulValue is too large, return one more than the highest valid index. */ BinaryIndexedTree.prototype.getIndex = function(cumulValue) { if (this.tree.length === 0 || this.tree[0] >= cumulValue) { return 0; } var index = 0; var mask = this.mask; var sum = this.tree[0]; while (mask !== 0) { var testIndex = index + mask; if (testIndex < this.tree.length && sum + this.tree[testIndex] < cumulValue) { index = testIndex; sum += this.tree[index]; } mask >>>= 1; } return index + 1; }; module.exports = BinaryIndexedTree;