gristlabs_grist-core/app/common/BinaryIndexedTree.js
Paul Fitzpatrick 5ef889addd (core) move home server into core
Summary: This moves enough server material into core to run a home server.  The data engine is not yet incorporated (though in manual testing it works when ported).

Test Plan: existing tests pass

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2552
2020-07-21 20:39:10 -04:00

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 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;