UNPKG

sketches-js-hassy

Version:

TypeScript implementation of DDSketch, a distributed quantile sketch algorithm

220 lines (219 loc) 8.24 kB
"use strict"; /* * Unless explicitly stated otherwise all files in this repository are licensed * under the Apache 2.0 license (see LICENSE). * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2020 Datadog, Inc. */ var __spreadArray = (this && this.__spreadArray) || function (to, from) { for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i]; return to; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DenseStore = void 0; var util_1 = require("./util"); var compiled_1 = require("../proto/compiled"); /** The default number of bins to grow when necessary */ var CHUNK_SIZE = 128; /** * `DenseStore` is a store that keeps all the bins between * the bin for the `minKey` and the `maxKey`. */ var DenseStore = /** @class */ (function () { /** * Initialize a new DenseStore * * @param chunkSize The number of bins to add each time the bins grow (default 128) */ function DenseStore(chunkSize) { if (chunkSize === void 0) { chunkSize = CHUNK_SIZE; } this.chunkSize = chunkSize; this.bins = []; this.count = 0; this.minKey = Infinity; this.maxKey = -Infinity; this.offset = 0; } /** * Update the counter at the specified index key, growing the number of bins if necessary * * @param key The key of the index to update * @param weight The amount to weight the key (default 1.0) */ DenseStore.prototype.add = function (key, weight) { if (weight === void 0) { weight = 1; } var index = this._getIndex(key); this.bins[index] += weight; this.count += weight; }; /** * Return the key for the value at the given rank * * E.g., if the non-zero bins are [1, 1] for keys a, b with no offset * * if lower = True: * keyAtRank(x) = a for x in [0, 1) * keyAtRank(x) = b for x in [1, 2) * if lower = False: * keyAtRank(x) = a for x in (-1, 0] * keyAtRank(x) = b for x in (0, 1] * * @param rank The rank at which to retrieve the key */ DenseStore.prototype.keyAtRank = function (rank, lower) { if (lower === void 0) { lower = true; } var runningCount = 0; for (var i = 0; i < this.length(); i++) { var bin = this.bins[i]; runningCount += bin; if ((lower && runningCount > rank) || (!lower && runningCount >= rank + 1)) { return i + this.offset; } } return this.maxKey; }; /** * Merge the contents of the parameter `store` into this store * * @param store The store to merge into the caller store */ DenseStore.prototype.merge = function (store) { if (store.count === 0) { return; } if (this.count === 0) { this.copy(store); return; } if (store.minKey < this.minKey || store.maxKey > this.maxKey) { this._extendRange(store.minKey, store.maxKey); } var collapseStartIndex = store.minKey - store.offset; var collapseEndIndex = Math.min(this.minKey, store.maxKey + 1) - store.offset; if (collapseEndIndex > collapseStartIndex) { var collapseCount = util_1.sumOfRange(store.bins, collapseStartIndex, collapseEndIndex); this.bins[0] += collapseCount; } else { collapseEndIndex = collapseStartIndex; } for (var key = collapseEndIndex + store.offset; key < store.maxKey + 1; key++) { this.bins[key - this.offset] += store.bins[key - store.offset]; } this.count += store.count; }; /** * Directly clone the contents of the parameter `store` into this store * * @param store The store to be copied into the caller store */ DenseStore.prototype.copy = function (store) { this.bins = __spreadArray([], store.bins); this.count = store.count; this.minKey = store.minKey; this.maxKey = store.maxKey; this.offset = store.offset; }; /** * Return the length of the underlying storage (`bins`) */ DenseStore.prototype.length = function () { return this.bins.length; }; DenseStore.prototype._getNewLength = function (newMinKey, newMaxKey) { var desiredLength = newMaxKey - newMinKey + 1; return this.chunkSize * Math.ceil(desiredLength / this.chunkSize); }; /** * Adjust the `bins`, the `offset`, the `minKey`, and the `maxKey` * without resizing the bins, in order to try to make it fit the specified range. * Collapse to the left if necessary */ DenseStore.prototype._adjust = function (newMinKey, newMaxKey) { this._centerBins(newMinKey, newMaxKey); this.minKey = newMinKey; this.maxKey = newMaxKey; }; /** Shift the bins by `shift`. This changes the `offset` */ DenseStore.prototype._shiftBins = function (shift) { var _a, _b; if (shift > 0) { this.bins = this.bins.slice(0, -shift); (_a = this.bins).unshift.apply(_a, new Array(shift).fill(0)); } else { this.bins = this.bins.slice(Math.abs(shift)); (_b = this.bins).push.apply(_b, new Array(Math.abs(shift)).fill(0)); } this.offset -= shift; }; /** Center the bins. This changes the `offset` */ DenseStore.prototype._centerBins = function (newMinKey, newMaxKey) { var middleKey = newMinKey + Math.floor((newMaxKey - newMinKey + 1) / 2); this._shiftBins(Math.floor(this.offset + this.length() / 2) - middleKey); }; /** Grow the bins as necessary, and call _adjust */ DenseStore.prototype._extendRange = function (key, secondKey) { var _a; secondKey = secondKey || key; var newMinKey = Math.min(key, secondKey, this.minKey); var newMaxKey = Math.max(key, secondKey, this.maxKey); if (this.length() === 0) { this.bins = new Array(this._getNewLength(newMinKey, newMaxKey)).fill(0); this.offset = newMinKey; this._adjust(newMinKey, newMaxKey); } else if (newMinKey >= this.minKey && newMaxKey < this.offset + this.length()) { // No need to change the range, just update the min and max keys this.minKey = newMinKey; this.maxKey = newMaxKey; } else { // Grow the bins var newLength = this._getNewLength(newMinKey, newMaxKey); if (newLength > this.length()) { (_a = this.bins).push.apply(_a, new Array(newLength - this.length()).fill(0)); } this._adjust(newMinKey, newMaxKey); } }; /** Calculate the bin index for the key, extending the range if necessary */ DenseStore.prototype._getIndex = function (key) { if (key < this.minKey) { this._extendRange(key); } else if (key > this.maxKey) { this._extendRange(key); } return key - this.offset; }; DenseStore.prototype.toProto = function () { return compiled_1.Store.create({ contiguousBinCounts: this.bins, contiguousBinIndexOffset: this.offset }); }; DenseStore.fromProto = function (protoStore) { if (!protoStore || /* Double equals (==) is intentional here to check for * `null` | `undefined` without including `0` */ protoStore.contiguousBinCounts == null || protoStore.contiguousBinIndexOffset == null) { throw Error('Failed to decode store from protobuf'); } var store = new this(); var index = protoStore.contiguousBinIndexOffset; store.offset = index; for (var _i = 0, _a = protoStore.contiguousBinCounts; _i < _a.length; _i++) { var count = _a[_i]; store.add(index, count); index += 1; } return store; }; return DenseStore; }()); exports.DenseStore = DenseStore;