bitset
Version:
A performance optimized infinite bit vector library
990 lines (787 loc) • 19.3 kB
JavaScript
/**
* @license BitSet v5.2.3 10/9/2024
* https://raw.org/article/javascript-bit-array/
*
* Copyright (c) 2024, Robert Eisele (https://raw.org/)
* Licensed under the MIT license.
**/
/**
* The number of bits of a word
* @const
* @type number
*/
var WORD_LENGTH = 32;
/**
* The log base 2 of WORD_LENGTH
* @const
* @type number
*/
var WORD_LOG = 5;
/**
* Calculates the number of set bits
*
* @param {number} v
* @returns {number}
*/
function popCount(v) {
// Warren, H. (2009). Hacker`s Delight. New York, NY: Addison-Wesley
v -= ((v >>> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
return (((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24);
}
/**
* Divide a number in base two by B
*
* @param {Array} arr
* @param {number} B
* @returns {number}
*/
function divide(arr, B) {
var r = 0;
for (var i = 0; i < arr.length; i++) {
r *= 2;
var d = (arr[i] + r) / B | 0;
r = (arr[i] + r) % B;
arr[i] = d;
}
return r;
}
/**
* Parses the parameters and set variable P
*
* @param {Object} P
* @param {string|BitSet|Array|Uint8Array|number=} val
*/
function parse(P, val) {
if (val == null) {
P['data'] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
P['_'] = 0;
return;
}
if (val instanceof BitSet) {
P['data'] = val['data'];
P['_'] = val['_'];
return;
}
switch (typeof val) {
case 'number':
P['data'] = [val | 0];
P['_'] = 0;
break;
case 'string':
var base = 2;
var len = WORD_LENGTH;
if (val.indexOf('0b') === 0) {
val = val.substr(2);
} else if (val.indexOf('0x') === 0) {
val = val.substr(2);
base = 16;
len = 8;
}
P['data'] = [];
P['_'] = 0;
var a = val.length - len;
var b = val.length;
do {
var num = parseInt(val.slice(a > 0 ? a : 0, b), base);
if (isNaN(num)) {
throw SyntaxError('Invalid param');
}
P['data'].push(num | 0);
if (a <= 0)
break;
a -= len;
b -= len;
} while (1);
break;
default:
P['data'] = [0];
var data = P['data'];
if (val instanceof Array) {
for (var i = val.length - 1; i >= 0; i--) {
var ndx = val[i];
if (ndx === Infinity) {
P['_'] = -1;
} else {
scale(P, ndx);
data[ndx >>> WORD_LOG] |= 1 << ndx;
}
}
break;
}
if (Uint8Array && val instanceof Uint8Array) {
var bits = 8;
scale(P, val.length * bits);
for (var i = 0; i < val.length; i++) {
var n = val[i];
for (var j = 0; j < bits; j++) {
var k = i * bits + j;
data[k >>> WORD_LOG] |= (n >> j & 1) << k;
}
}
break;
}
throw SyntaxError('Invalid param');
}
}
/**
* Module entry point
*
* @constructor
* @param {string|BitSet|number=} param
* @returns {BitSet}
*/
function BitSet(param) {
if (!(this instanceof BitSet)) {
return new BitSet(param);
}
parse(this, param);
this['data'] = this['data'].slice();
}
function scale(dst, ndx) {
var l = ndx >>> WORD_LOG;
var d = dst['data'];
var v = dst['_'];
for (var i = d.length; l >= i; l--) {
d.push(v);
}
}
var P = {
'data': [], // Holds the actual bits in form of a 32bit integer array.
'_': 0 // Holds the MSB flag information to make indefinitely large bitsets inversion-proof
};
BitSet.prototype = {
'data': [],
'_': 0,
/**
* Set a single bit flag
*
* Ex:
* bs1 = new BitSet(10);
*
* bs1.set(3, 1);
*
* @param {number} ndx The index of the bit to be set
* @param {number=} value Optional value that should be set on the index (0 or 1)
* @returns {BitSet} this
*/
'set': function (ndx, value) {
ndx |= 0;
scale(this, ndx);
if (value === undefined || value) {
this['data'][ndx >>> WORD_LOG] |= (1 << ndx);
} else {
this['data'][ndx >>> WORD_LOG] &= ~(1 << ndx);
}
return this;
},
/**
* Get a single bit flag of a certain bit position
*
* Ex:
* bs1 = new BitSet();
* var isValid = bs1.get(12);
*
* @param {number} ndx the index to be fetched
* @returns {number} The binary flag
*/
'get': function (ndx) {
ndx |= 0;
var d = this['data'];
var n = ndx >>> WORD_LOG;
if (n >= d.length) {
return this['_'] & 1;
}
return (d[n] >>> ndx) & 1;
},
/**
* Creates the bitwise NOT of a set.
*
* Ex:
* bs1 = new BitSet(10);
*
* res = bs1.not();
*
* @returns {BitSet} A new BitSet object, containing the bitwise NOT of this
*/
'not': function () { // invert()
var t = this['clone']();
var d = t['data'];
for (var i = 0; i < d.length; i++) {
d[i] = ~d[i];
}
t['_'] = ~t['_'];
return t;
},
/**
* Creates the bitwise AND of two sets.
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = new BitSet(10);
*
* res = bs1.and(bs2);
*
* @param {BitSet} value A bitset object
* @returns {BitSet} A new BitSet object, containing the bitwise AND of this and value
*/
'and': function (value) {// intersection
parse(P, value);
var T = this['clone']();
var t = T['data'];
var p = P['data'];
var pl = p.length;
var p_ = P['_'];
var t_ = T['_'];
// If this is infinite, we need all bits from P
if (t_ !== 0) {
scale(T, pl * WORD_LENGTH - 1);
}
var tl = t.length;
var l = Math.min(pl, tl);
var i = 0;
for (; i < l; i++) {
t[i] &= p[i];
}
for (; i < tl; i++) {
t[i] &= p_;
}
T['_'] &= p_;
return T;
},
/**
* Creates the bitwise OR of two sets.
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = new BitSet(10);
*
* res = bs1.or(bs2);
*
* @param {BitSet} val A bitset object
* @returns {BitSet} A new BitSet object, containing the bitwise OR of this and val
*/
'or': function (val) { // union
parse(P, val);
var t = this['clone']();
var d = t['data'];
var p = P['data'];
var pl = p.length - 1;
var tl = d.length - 1;
var minLength = Math.min(tl, pl);
// Append backwards, extend array only once
for (var i = pl; i > minLength; i--) {
d[i] = p[i];
}
for (; i >= 0; i--) {
d[i] |= p[i];
}
t['_'] |= P['_'];
return t;
},
/**
* Creates the bitwise XOR of two sets.
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = new BitSet(10);
*
* res = bs1.xor(bs2);
*
* @param {BitSet} val A bitset object
* @returns {BitSet} A new BitSet object, containing the bitwise XOR of this and val
*/
'xor': function (val) { // symmetric difference
parse(P, val);
var t = this['clone']();
var d = t['data'];
var p = P['data'];
var t_ = t['_'];
var p_ = P['_'];
var i = 0;
var tl = d.length - 1;
var pl = p.length - 1;
// Cut if tl > pl
for (i = tl; i > pl; i--) {
d[i] ^= p_;
}
// Cut if pl > tl
for (i = pl; i > tl; i--) {
d[i] = t_ ^ p[i];
}
// XOR the rest
for (; i >= 0; i--) {
d[i] ^= p[i];
}
// XOR infinity
t['_'] ^= p_;
return t;
},
/**
* Creates the bitwise AND NOT (not confuse with NAND!) of two sets.
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = new BitSet(10);
*
* res = bs1.notAnd(bs2);
*
* @param {BitSet} val A bitset object
* @returns {BitSet} A new BitSet object, containing the bitwise AND NOT of this and other
*/
'andNot': function (val) { // difference
return this['and'](new BitSet(val)['flip']());
},
/**
* Flip/Invert a range of bits by setting
*
* Ex:
* bs1 = new BitSet();
* bs1.flip(); // Flip entire set
* bs1.flip(5); // Flip single bit
* bs1.flip(3,10); // Flip a bit range
*
* @param {number=} from The start index of the range to be flipped
* @param {number=} to The end index of the range to be flipped
* @returns {BitSet} this
*/
'flip': function (from, to) {
if (from === undefined) {
var d = this['data'];
for (var i = 0; i < d.length; i++) {
d[i] = ~d[i];
}
this['_'] = ~this['_'];
} else if (to === undefined) {
scale(this, from);
this['data'][from >>> WORD_LOG] ^= (1 << from);
} else if (0 <= from && from <= to) {
scale(this, to);
for (var i = from; i <= to; i++) {
this['data'][i >>> WORD_LOG] ^= (1 << i);
}
}
return this;
},
/**
* Clear a range of bits by setting it to 0
*
* Ex:
* bs1 = new BitSet();
* bs1.clear(); // Clear entire set
* bs1.clear(5); // Clear single bit
* bs1.clear(3,10); // Clear a bit range
*
* @param {number=} from The start index of the range to be cleared
* @param {number=} to The end index of the range to be cleared
* @returns {BitSet} this
*/
'clear': function (from, to) {
var data = this['data'];
if (from === undefined) {
for (var i = data.length - 1; i >= 0; i--) {
data[i] = 0;
}
this['_'] = 0;
} else if (to === undefined) {
from |= 0;
scale(this, from);
data[from >>> WORD_LOG] &= ~(1 << from);
} else if (from <= to) {
scale(this, to);
for (var i = from; i <= to; i++) {
data[i >>> WORD_LOG] &= ~(1 << i);
}
}
return this;
},
/**
* Gets an entire range as a new bitset object
*
* Ex:
* bs1 = new BitSet();
* bs1.slice(4, 8);
*
* @param {number=} from The start index of the range to be get
* @param {number=} to The end index of the range to be get
* @returns {BitSet} A new smaller bitset object, containing the extracted range
*/
'slice': function (from, to) {
if (from === undefined) {
return this['clone']();
} else if (to === undefined) {
to = this['data'].length * WORD_LENGTH;
var im = Object.create(BitSet.prototype);
im['_'] = this['_'];
im['data'] = [0];
for (var i = from; i <= to; i++) {
im['set'](i - from, this['get'](i));
}
return im;
} else if (from <= to && 0 <= from) {
var im = Object.create(BitSet.prototype);
im['data'] = [0];
for (var i = from; i <= to; i++) {
im['set'](i - from, this['get'](i));
}
return im;
}
return null;
},
/**
* Set a range of bits
*
* Ex:
* bs1 = new BitSet();
*
* bs1.setRange(10, 15, 1);
*
* @param {number} from The start index of the range to be set
* @param {number} to The end index of the range to be set
* @param {number} value Optional value that should be set on the index (0 or 1)
* @returns {BitSet} this
*/
'setRange': function (from, to, value) {
for (var i = from; i <= to; i++) {
this['set'](i, value);
}
return this;
},
/**
* Clones the actual object
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = bs1.clone();
*
* @returns {BitSet|Object} A new BitSet object, containing a copy of the actual object
*/
'clone': function () {
var im = Object.create(BitSet.prototype);
im['data'] = this['data'].slice();
im['_'] = this['_'];
return im;
},
/**
* Gets a list of set bits
*
* @returns {Array}
*/
'toArray': Math['clz32'] ?
function () {
var ret = [];
var data = this['data'];
for (var i = data.length - 1; i >= 0; i--) {
var num = data[i];
while (num !== 0) {
var t = 31 - Math['clz32'](num);
num ^= 1 << t;
ret.unshift((i * WORD_LENGTH) + t);
}
}
if (this['_'] !== 0)
ret.push(Infinity);
return ret;
} :
function () {
var ret = [];
var data = this['data'];
for (var i = 0; i < data.length; i++) {
var num = data[i];
while (num !== 0) {
var t = num & -num;
num ^= t;
ret.push((i * WORD_LENGTH) + popCount(t - 1));
}
}
if (this['_'] !== 0)
ret.push(Infinity);
return ret;
},
/**
* Overrides the toString method to get a binary representation of the BitSet
*
* @param {number=} base
* @returns string A binary string
*/
'toString': function (base) {
var data = this['data'];
if (!base)
base = 2;
// If base is power of two
if ((base & (base - 1)) === 0 && base < 36) {
var ret = '';
var len = 2 + Math.log(4294967295/*Math.pow(2, WORD_LENGTH)-1*/) / Math.log(base) | 0;
for (var i = data.length - 1; i >= 0; i--) {
var cur = data[i];
// Make the number unsigned
if (cur < 0)
cur += 4294967296 /*Math.pow(2, WORD_LENGTH)*/;
var tmp = cur.toString(base);
if (ret !== '') {
// Fill small positive numbers with leading zeros. The +1 for array creation is added outside already
ret += '0'.repeat(len - tmp.length - 1);
}
ret += tmp;
}
if (this['_'] === 0) {
ret = ret.replace(/^0+/, '');
if (ret === '')
ret = '0';
return ret;
} else {
// Pad the string with ones
ret = '1111' + ret;
return ret.replace(/^1+/, '...1111');
}
} else {
if ((2 > base || base > 36))
throw SyntaxError('Invalid base');
var ret = [];
var arr = [];
// Copy every single bit to a new array
for (var i = data.length; i--;) {
for (var j = WORD_LENGTH; j--;) {
arr.push(data[i] >>> j & 1);
}
}
do {
ret.unshift(divide(arr, base).toString(base));
} while (!arr.every(function (x) {
return x === 0;
}));
return ret.join('');
}
},
/**
* Check if the BitSet is empty, means all bits are unset
*
* Ex:
* bs1 = new BitSet(10);
*
* bs1.isEmpty() ? 'yes' : 'no'
*
* @returns {boolean} Whether the bitset is empty
*/
'isEmpty': function () {
if (this['_'] !== 0)
return false;
var d = this['data'];
for (var i = d.length - 1; i >= 0; i--) {
if (d[i] !== 0)
return false;
}
return true;
},
/**
* Calculates the number of bits set
*
* Ex:
* bs1 = new BitSet(10);
*
* var num = bs1.cardinality();
*
* @returns {number} The number of bits set
*/
'cardinality': function () {
if (this['_'] !== 0) {
return Infinity;
}
var s = 0;
var d = this['data'];
for (var i = 0; i < d.length; i++) {
var n = d[i];
if (n !== 0)
s += popCount(n);
}
return s;
},
/**
* Calculates the Most Significant Bit / log base two
*
* Ex:
* bs1 = new BitSet(10);
*
* var logbase2 = bs1.msb();
*
* var truncatedTwo = Math.pow(2, logbase2); // May overflow!
*
* @returns {number} The index of the highest bit set
*/
'msb': Math['clz32'] ?
function () {
if (this['_'] !== 0) {
return Infinity;
}
var data = this['data'];
for (var i = data.length; i-- > 0;) {
var c = Math['clz32'](data[i]);
if (c !== WORD_LENGTH) {
return (i * WORD_LENGTH) + WORD_LENGTH - 1 - c;
}
}
return Infinity;
} :
function () {
if (this['_'] !== 0) {
return Infinity;
}
var data = this['data'];
for (var i = data.length; i-- > 0;) {
var v = data[i];
var c = 0;
if (v) {
for (; (v >>>= 1) > 0; c++) {
}
return (i * WORD_LENGTH) + c;
}
}
return Infinity;
},
/**
* Calculates the number of trailing zeros
*
* Ex:
* bs1 = new BitSet(10);
*
* var ntz = bs1.ntz();
*
* @returns {number} The index of the lowest bit set
*/
'ntz': function () {
var data = this['data'];
for (var j = 0; j < data.length; j++) {
var v = data[j];
if (v !== 0) {
v = (v ^ (v - 1)) >>> 1; // Set v's trailing 0s to 1s and zero rest
return (j * WORD_LENGTH) + popCount(v);
}
}
return Infinity;
},
/**
* Calculates the Least Significant Bit
*
* Ex:
* bs1 = new BitSet(10);
*
* var lsb = bs1.lsb();
*
* @returns {number} The index of the lowest bit set
*/
'lsb': function () {
var data = this['data'];
for (var i = 0; i < data.length; i++) {
var v = data[i];
var c = 0;
if (v) {
var bit = (v & -v);
for (; (bit >>>= 1); c++) {
}
return WORD_LENGTH * i + c;
}
}
return this['_'] & 1;
},
/**
* Compares two BitSet objects
*
* Ex:
* bs1 = new BitSet(10);
* bs2 = new BitSet(10);
*
* bs1.equals(bs2) ? 'yes' : 'no'
*
* @param {BitSet} val A bitset object
* @returns {boolean} Whether the two BitSets have the same bits set (valid for indefinite sets as well)
*/
'equals': function (val) {
parse(P, val);
var t = this['data'];
var p = P['data'];
var t_ = this['_'];
var p_ = P['_'];
var tl = t.length - 1;
var pl = p.length - 1;
if (p_ !== t_) {
return false;
}
var minLength = tl < pl ? tl : pl;
var i = 0;
for (; i <= minLength; i++) {
if (t[i] !== p[i])
return false;
}
for (i = tl; i > pl; i--) {
if (t[i] !== p_)
return false;
}
for (i = pl; i > tl; i--) {
if (p[i] !== t_)
return false;
}
return true;
},
[Symbol.iterator]: function () {
var d = this['data'];
var ndx = 0;
if (this['_'] === 0) {
// Find highest index with something meaningful
var highest = 0;
for (var i = d.length - 1; i >= 0; i--) {
if (d[i] !== 0) {
highest = i;
break;
}
}
return {
'next': function () {
var n = ndx >>> WORD_LOG;
return {
'done': n > highest || n === highest && (d[n] >>> ndx) === 0,
'value': n > highest ? 0 : (d[n] >>> ndx++) & 1
};
}
};
} else {
// Endless iterator!
return {
'next': function () {
var n = ndx >>> WORD_LOG;
return {
'done': false,
'value': n < d.length ? (d[n] >>> ndx++) & 1 : 1,
};
}
};
}
}
};
BitSet['fromBinaryString'] = function (str) {
return new BitSet('0b' + str);
};
BitSet['fromHexString'] = function (str) {
return new BitSet('0x' + str);
};
BitSet['Random'] = function (n) {
if (n === undefined || n < 0) {
n = WORD_LENGTH;
}
var m = n % WORD_LENGTH;
// Create an array, large enough to hold the random bits
var t = [];
var len = Math.ceil(n / WORD_LENGTH);
// Create an bitset instance
var s = Object.create(BitSet.prototype);
// Fill the vector with random data, uniformly distributed
for (var i = 0; i < len; i++) {
t.push(Math.random() * 4294967296 | 0);
}
// Mask out unwanted bits
if (m > 0) {
t[len - 1] &= (1 << m) - 1;
}
s['data'] = t;
s['_'] = 0;
return s;
};