src/BitArray.js
/**
* A {@link BitSet} implementation with no limit due to bits being stored in an array. Also known as bit set, bit map
* or bit vector. This implementation will never throw out of bounds errors.
*
* @public
* @class
* @implements {BitSet}
*/
class BitArray {
/**
* An array containing each bit as a boolean value, starting from the rightmost bit.
*
* @private
* @member {Array<Boolean>}
*/
value;
/**
* @public
* @constructor
* @param {Number} [minLength = 1] The minimum length of the bitArray.
* @throws {Error} In case 'minLength' is equals to or smaller than zero.
*/
constructor(minLength) {
if (minLength !== undefined && minLength <= 0) {
throw Error('Illegal argument: parameter \'minLength\' must be larger than 0');
}
minLength = minLength || 1;
this.value = [];
for (let i = 0; i < minLength; i++) {
this.value.push(false);
}
}
/**
* Combines masks. This is equivalent to a OR operation.
*
* @private
* @static
* @param {...BitMask} masks The masks to combine.
* @returns {BitArray} The resulting mask.
*/
static combineMasks(...masks) {
const bitArray = new BitArray();
const tempArray = new BitArray();
for (const mask of masks) {
tempArray.copy(mask);
for (let i = 0; i < tempArray.length; i++) {
const oldBit = bitArray.get(i);
const newBit = tempArray.get(i);
bitArray.setAt(oldBit || newBit, i);
}
}
return bitArray;
}
/**
* Produces a new BitArray instance from a value. The value may contain anything, the resulting bitArray is based
* on the truthiness of the value contents.
* Example: [true, 0, {}] will yield 101.
*
* @public
* @static
* @param {Array<*>} array
* @returns {BitArray} A new BitArray instance.
*/
static fromArray(array) {
const bitArray = new BitArray();
bitArray.value = array.slice(0).reverse().map(Boolean);
return bitArray;
}
get length() {
return this.value.length;
}
count() {
return this.value.filter(value => value).length;
}
intersect(...masks) {
const bitArray = BitArray.combineMasks(...masks);
const length = Math.max(bitArray.length, this.length);
for (let i = 0; i < length; i++) {
const thisBit = this.get(i);
const otherBit = bitArray.get(i);
this.setAt(thisBit && otherBit, i);
}
return this;
}
intersects(...masks) {
const bitArray = BitArray.combineMasks(...masks);
const length = Math.max(bitArray.length, this.length);
for (let i = 0; i < length; i++) {
const thisBit = this.get(i);
const otherBit = bitArray.get(i);
if (thisBit && otherBit) {
return true;
}
}
return false;
}
get(index) {
return this.value[index] || false;
}
getRange(from, to) {
if (to <= from) {
throw Error('Illegal argument: parameter \'to\' must be larger than parameter \'from\'');
}
const length = to - from;
const bitArray = new BitArray();
bitArray.value = this.value.slice(from, to);
while (bitArray.length < length) {
bitArray.value.push(false);
}
return bitArray;
}
test(...masks) {
const mask = BitArray.combineMasks(...masks);
for (let i = 0; i < mask.length; i++) {
const bit = mask.get(i);
if (bit && !this.get(i)) {
return false;
}
}
return true;
}
testAny(...masks) {
const mask = BitArray.combineMasks(...masks);
for (let i = 0; i < mask.length; i++) {
const bit = mask.get(i);
if (bit && this.get(i)) {
return true;
}
}
return false;
}
testAt(value, index) {
if (index > this.length) {
return Boolean(value) === false;
}
return Boolean(value) === this.get(index);
}
testAll(value) {
value = Boolean(value);
return this.value.every(element => element === value);
}
on(...masks) {
return this.set(1, ...masks);
}
off(...masks) {
return this.set(0, ...masks);
}
set(value, ...masks) {
const mask = BitArray.combineMasks(...masks);
for (let i = 0; i < mask.length; i++) {
const bit = mask.get(i);
if (bit) {
this.setAt(value, i);
}
}
return this;
}
setAll(value) {
this.value.fill(Boolean(value));
return this;
}
setAt(value, index) {
while (index >= this.length) {
this.value.push(false);
}
this.value[index] = Boolean(value);
return this;
}
setRange(value, from, to) {
if (to <= from) {
throw Error('Illegal argument: parameter \'to\' must be larger than parameter \'from\'');
}
for (let i = from; i < to; i++) {
this.setAt(value, i);
}
return this;
}
flip(...masks) {
const mask = BitArray.combineMasks(...masks);
for (let i = 0; i < mask.length; i++) {
const bit = mask.get(i);
if (bit) {
this.flipAt(i);
}
}
return this;
}
flipAll() {
for (let i = 0; i < this.length; i++) {
this.flipAt(i);
}
return this;
}
flipAt(index) {
while (index >= this.length) {
this.value.push(false);
}
this.setAt(!this.get(index), index);
return this;
}
flipRange(from, to) {
if (to <= from) {
throw Error('Illegal argument: parameter \'to\' must be larger than parameter \'from\'');
}
for (let i = from; i < to; i++) {
this.flipAt(i);
}
return this;
}
copy(bitset) {
this.value.fill(false);
if (bitset instanceof Object) {
const array = bitset.toArray().reverse();
this.value.splice(0, array.count, ...array);
} else {
let index = 0;
while (bitset > 0) {
this.setAt(bitset & 1, index);
bitset >>= 1;
index++;
}
}
return this;
}
/**
* Gets the integer value of this instance.
*
* @public
* @throws {Error} In case this instance cannot be represented by an integer (by exceeding 31 bits).
* @returns {Number}
*/
valueOf() {
if (this.length > 31) {
throw new Error('Number exceeds 31 bits');
}
return this.toArray().reduce((prev, curr) => {
prev <<= 1;
if (curr) {
prev++;
}
return prev;
}, 0);
}
serialize() {
return this.toArray().map(Number).join('');
}
/**
* Deserializes a string and returns a new instance.
*
* @public
* @static
* @param {String} input
* @throws {Error} In case input could not be parsed.
* @returns {BitArray} A new instance.
*/
static deserialize(input) {
const array = input.split('').map(Number);
if (array.some(isNaN)) {
throw new Error('Failed to deserialize input');
}
return BitArray.fromArray(array);
}
clone() {
return new BitArray(this.length).copy(this);
}
equals(other) {
const bitArray = new BitArray().copy(other);
const length = Math.max(this.length, bitArray.length);
for (let i = 0; i < length; i++) {
if (this.get(i) !== bitArray.get(i)) {
return false;
}
}
return true;
}
toArray() {
return this.value.slice(0).reverse();
}
toString() {
return `BitArray(${this.serialize()})`;
}
}
export default BitArray;