bitlist
Version:
A bit-set/bit-array/bit-vector implementation which allows inserting and removing values
286 lines (257 loc) • 7.89 kB
JavaScript
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);
}
*/