UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

187 lines (149 loc) 4.43 kB
/** * Mnemonist Bloom Filter * ======================= * * Bloom Filter implementation relying on MurmurHash3. */ var murmurhash3 = require('./utils/murmurhash3.js'), forEach = require('obliterator/foreach'); /** * Constants. */ var LN2_SQUARED = Math.LN2 * Math.LN2; /** * Defaults. */ var DEFAULTS = { errorRate: 0.005 }; /** * Function used to convert a string into a Uint16 byte array. * * @param {string} string - Target string. * @return {Uint16Array} */ function stringToByteArray(string) { var array = new Uint16Array(string.length), i, l; for (i = 0, l = string.length; i < l; i++) array[i] = string.charCodeAt(i); return array; } /** * Function used to hash the given byte array. * * @param {number} length - Length of the filter's byte array. * @param {number} seed - Seed to use for the hash function. * @param {Uint16Array} - Byte array representing the string. * @return {number} - The hash. * * @note length * 8 should probably already be computed as well as seeds. */ function hashArray(length, seed, array) { var hash = murmurhash3((seed * 0xFBA4C795) & 0xFFFFFFFF, array); return hash % (length * 8); } /** * Bloom Filter. * * @constructor * @param {number|object} capacityOrOptions - Capacity or options. */ function BloomFilter(capacityOrOptions) { var options = {}; if (!capacityOrOptions) throw new Error('mnemonist/BloomFilter.constructor: a BloomFilter must be created with a capacity.'); if (typeof capacityOrOptions === 'object') options = capacityOrOptions; else options.capacity = capacityOrOptions; // Handling capacity if (typeof options.capacity !== 'number' || options.capacity <= 0) throw new Error('mnemonist/BloomFilter.constructor: `capacity` option should be a positive integer.'); this.capacity = options.capacity; // Handling error rate this.errorRate = options.errorRate || DEFAULTS.errorRate; if (typeof this.errorRate !== 'number' || options.errorRate <= 0) throw new Error('mnemonist/BloomFilter.constructor: `errorRate` option should be a positive float.'); this.clear(); } /** * Method used to clear the filter. * * @return {undefined} */ BloomFilter.prototype.clear = function() { // Optimizing number of bits & number of hash functions var bits = -1 / LN2_SQUARED * this.capacity * Math.log(this.errorRate), length = (bits / 8) | 0; this.hashFunctions = (length * 8 / this.capacity * Math.LN2) | 0; // Creating the data array this.data = new Uint8Array(length); return; }; /** * Method used to add an string to the filter. * * @param {string} string - Item to add. * @return {BloomFilter} * * @note Should probably create a hash function working directly on a string. */ BloomFilter.prototype.add = function(string) { // Converting the string to a byte array var array = stringToByteArray(string); // Applying the n hash functions for (var i = 0, l = this.hashFunctions; i < l; i++) { var index = hashArray(this.data.length, i, array), position = (1 << (7 & index)); this.data[index >> 3] |= position; } return this; }; /** * Method used to test the given string. * * @param {string} string - Item to test. * @return {boolean} */ BloomFilter.prototype.test = function(string) { // Converting the string to a byte array var array = stringToByteArray(string); // Applying the n hash functions for (var i = 0, l = this.hashFunctions; i < l; i++) { var index = hashArray(this.data.length, i, array); if (!(this.data[index >> 3] & (1 << (7 & index)))) return false; } return true; }; /** * Convenience known methods. */ BloomFilter.prototype.toJSON = function() { return this.data; }; /** * Static @.from function taking an arbitrary iterable & converting it into * a filter. * * @param {Iterable} iterable - Target iterable. * @return {BloomFilter} */ BloomFilter.from = function(iterable, options) { if (!options) { options = iterable.length || iterable.size; if (typeof options !== 'number') throw new Error('BloomFilter.from: could not infer the filter\'s capacity. Try passing it as second argument.'); } var filter = new BloomFilter(options); forEach(iterable, function(value) { filter.add(value); }); return filter; }; /** * Exporting. */ module.exports = BloomFilter;