UNPKG

bitlist

Version:

A bit-set/bit-array/bit-vector implementation which allows inserting and removing values

286 lines (257 loc) 7.89 kB
var TOP_BIT = 1 << 31; module.exports = BitList; /** * Create a BitList object. * Can be pre-populated with values by passing an array of Booleans. * @param {Array.<Boolean>=} values */ function BitList(values) { this.size = 0; this._data = []; if (values) { for (var i = 0; i < values.length; ++i) { this.insert(i, values[i]); } } } /** * Create a new BitList by AND'ing one or more BitLists together. * @param {Array.<BitList>} lists * @return {BitList} */ BitList.combine = function (lists) { var list = lists[0].clone(); list.applyAnd(lists.slice(1)); return list; }; BitList.prototype = { /** * Get the value at the given index. * @param {Number} index Must be in the range 0..this.size - 1 * @return {Boolean} */ get: function (index) { return (this._data[(index / 32) | 0] & (1 << (index % 32))) !== 0; }, /** * Update the value at the given index. * @param {Number} index Must be in the range 0..this.size - 1 * @param {Boolean} value */ set: function (index, value) { if (index < 0 || index >= this.size) { return; } var bufferOffset = (index / 32) | 0; var bitOffset = index % 32; this._data[bufferOffset] ^= ((!value - 1) ^ this._data[bufferOffset]) & (1 << bitOffset); }, /** * Insert a new value into the list at the given position. * @param {Number} index Must be in the range 0..this.size * @param {Boolean} value */ insert: function (index, value) { if (index > this.size || index < 0) { return; } var bufferOffset; var bitOffset; var currBlock; var bitRange; var i; if (this.size % 32 === 0) { growBuffer.call(this); } // make space in the current block if (index < this.size) { bufferOffset = (index / 32) | 0; bitOffset = index % 32; currBlock = this._data[bufferOffset]; // shift all the values in higher blocks up one, checking the top bit of the previous block to see what the value // of the lowest bit in this block should now be. for (i = this._data.length - 1; i > bufferOffset; --i) { this._data[i] <<= 1; if (this._data[i - 1] & TOP_BIT) { this._data[i] |= 1; } } // if bitOffset = 5, this is 11111111111111111111111111100000 bitRange = createBitRange(0, bitOffset - 1, false); this._data[bufferOffset] = currBlock << 1 & bitRange | (currBlock & ~bitRange); } // else, do nothing, we'll just assign into the space ++this.size; this.set(index, value); }, /** * Remove a value from the list. * @param {Number} index Must be in the range 0..this.size-1 */ remove: function (index) { if (index >= this.size || index < 0) { return; } var bufferOffset; var bitOffset; var currBlock; var bitRange; var i; if (index < this.size - 1) { bufferOffset = (index / 32) | 0; bitOffset = index % 32; currBlock = this._data[bufferOffset]; bitRange = createBitRange(0, bitOffset - 1, false); this._data[bufferOffset] = currBlock >>> 1 & bitRange | (currBlock & ~bitRange); for (i = bufferOffset + 1; i < this._data.length; ++i) { if (this._data[i] & 1) { this._data[i - 1] |= TOP_BIT; } this._data[i] >>>= 1; // shift right, make leftmost bit 0 } } // else, do nothing... the value can stay there for all we care... if (--this.size % 32 === 0) { shrinkBuffer.call(this); } }, /** * Apply one or more other BitLists to the current one, using AND operations (treats each as a mask). * @param {Array.<BitList>} lists */ applyAnd: function (lists) { for (var i = 0; i < lists.length; ++i) { applyAndMask.call(this, lists[i]); } }, /** * Set a range of values to false. Indexes are inclusive. * @param {Number} lowIndex * @param {Number} highIndex */ clearRange: function (lowIndex, highIndex) { var lowBlock = lowIndex / 32 | 0; var highBlock = highIndex / 32 | 0; var low; var high; var i; for (i = lowBlock; i <= highBlock; ++i) { low = Math.max(0, lowIndex - i * 32); high = Math.min(31, highIndex - i * 32); if (low === 0 && high === 31) { this._data[i] = 0; } else { this._data[i] &= createBitRange(low, high, 0); } } }, /** * Create a new BitList which has the same values as this one. * @return {BitList} */ clone: function () { var list = new BitList(); list._data = this._data.slice(); list.size = this.size; return list; }, /** * Turn the BitList into an array of indexes which have the given value. * @example * [1,0,1,1,0].getIndexes(false) -> [1, 4] * [1,0,1,1,0].getIndexes(true) -> [0, 2, 3] * @param {Boolean} matchValue Items which have this value will be added to the return list * @return {Array.<Number>} */ getIndexes: function (matchValue) { var out = []; var value = !!matchValue; for (var i = 0; i < this.size; ++i) { if (this.get(i) === value) { out.push(i); } } return out; }, /** * Count the number of items in the given range which have the given value. Indexes are inclusive. * @param {Boolean} value Counts indexes with this value. * @param {Number=} lowIndex The start index of the range to check. Defaults to 0. * @param {Number=} highIndex The end index of the range to check. Defaults to the size of the list. * @return {Number} */ countValuesInRange: function (value, lowIndex, highIndex) { lowIndex = lowIndex || 0; highIndex = highIndex === undefined ? this.size - 1 : highIndex; var lowBlock = lowIndex / 32 | 0; var highBlock = highIndex / 32 | 0; var low; var high; var i; var rangeVal; var count = 0; for (i = lowBlock; i <= highBlock; ++i) { low = Math.max(0, lowIndex - i * 32); high = Math.min(31, highIndex - i * 32); rangeVal = (low === 0 && high === 31) // if it covers the whole block ? this._data[i] : this._data[i] & createBitRange(low, high, 1); count += numberOfSetBits(rangeVal); // count the 1s } return value ? count : highIndex - lowIndex - count + 1; } }; function growBuffer() { this._data.push(0); } function shrinkBuffer() { this._data.pop(); } function applyAndMask(list) { for (var d = 0; d < this._data.length; ++d) { this._data[d] &= list._data[d]; } } // numberOfSetBits(13) --> (13 = 0b1101) -> 3 // num 1101 count = 0 // num - 1 1100 // &= 1100 count = 1 // num - 1 1011 // &= 1000 count = 2 // num - 1 0111 // &= 0000 count = 3 function numberOfSetBits(num) { for (var count = 0; num; ++count) { num &= num - 1; } return count; } // create a 32-bit mask with the indices low->high (inclusive) set to `value` (and the other values not) // createBitRange(5, 20, true) -> 00000000000111111111111111100000 // createBitRange(0, 1, false) -> 11111111111111111111111111111100 function createBitRange(low, high, value) { var range = ((1 << (high - low + 1)) - 1) << low; if (!value) { range = ~range; } return range; } /* Some functions which are useful for debugging lists and bitwise values // display a number in binary function b(n) { return pad(dec2Bin((dec >>> 0).toString(2))); } // display a list's values and other info function showList(m) { return '\n' + repeat(' ', m._data.length * 32 - m.size) + '| ' + m.size + '\n' + m._data.map(b).reverse().join('') + '\n' + m._data.map(function (__, index) { return repeat(' ', 31) + (m._data.length - index - 1); }).join(''); } function pad(s) { return (repeat('0', 32) + s).slice(-32); } function repeat(s, n) { return (new Array(n + 1)).join(s); } */