mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
259 lines
7.5 KiB
JavaScript
259 lines
7.5 KiB
JavaScript
/**
|
|
* 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 individual 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;
|