diff --git a/src/bitmap.ts b/src/bitmap.ts index 6ac3d9e..6cd0d5c 100644 --- a/src/bitmap.ts +++ b/src/bitmap.ts @@ -1,15 +1,58 @@ const computeBitsArrayLength = (x: number): number => Math.ceil(x / 8); +/** + * Compute the number of bytes required to store `x` bits. + * @private + * @param {number} x - Number of bits to accommodate. + * @returns {number} Number of bytes required. + */ + +/** + * Get the byte index that contains the bit for position `x`. + * @private + * @param {number} x - Bit index. + * @returns {number} Byte index in the underlying Uint8Array. + */ const getBitPositionIndex = (x: number): number => Math.floor(x / 8); + +/** + * Get the position of the bit inside its byte for bit index `x`. + * @private + * @param {number} x - Bit index. + * @returns {number} Bit position within the byte (0-7). + */ const getBitPosition = (x: number): number => x % 8; + +/** + * Create an 8-bit mask with a 1 at `bitIdx`. + * @private + * @param {number} bitIdx - Bit position inside a byte (0-7). + * @returns {number} Mask with the bit at `bitIdx` set (e.g. 0b00001000). + */ const getMask = (bitIdx: number): number => 0b00000001 << bitIdx; export class Bitmap { private bits: Uint8Array; + /** + * Bitmap - a simple bitset backed by a Uint8Array. + * Indices are zero-based. + * @example + * const b = new Bitmap(64); // capacity for 64 bits + * b.set(10); + * b.set(20); + * console.log(b.contains(10)); // true + * + * @param {number} [x=32] - Initial capacity in bits (defaults to 32). + */ constructor(x = 32) { this.bits = new Uint8Array(computeBitsArrayLength(x)); } + /** + * Iterate over all set bit indices in ascending order. + * Yields the numeric index of each set bit. + * @yields {number} The index of a set bit (0-based). + */ public *[Symbol.iterator](): Generator { for (let i = 0; i < this.bits.length; i++) { const byte = this.bits[i]; @@ -22,6 +65,12 @@ export class Bitmap { } } + /** + * Ensure the underlying array can hold at least up to bit index `x`. + * This may reallocate the underlying Uint8Array, keeping existing bits. + * @param {number} x - Bit index to accommodate. + * @returns {void} + */ public grow(x: number): void { const arrLength = Math.max(1, computeBitsArrayLength(x + 1)); if (arrLength <= this.bits.length) return; @@ -30,12 +79,24 @@ export class Bitmap { this.bits.set(prev); } + /** + * Set the bit at index `x` to 1. + * Automatically grows the internal buffer if necessary. + * @param {number} x - Bit index to set (0-based). + * @returns {void} + */ public set(x: number): void { const idx = getBitPositionIndex(x); if (idx >= this.bits.length) this.grow(x); this.bits[idx] |= getMask(getBitPosition(x)); } + /** + * Clear the bit at index `x` (set to 0). + * If `x` is beyond current capacity, the call is a no-op. + * @param {number} x - Bit index to clear (0-based). + * @returns {void} + */ public remove(x: number): void { const idx = getBitPositionIndex(x); if (idx >= this.bits.length) return; @@ -43,6 +104,11 @@ export class Bitmap { this.bits[idx] &= ~mask; } + /** + * Check whether the bit at index `x` is set. + * @param {number} x - Bit index to test (0-based). + * @returns {boolean} True if the bit is 1, false otherwise. + */ public contains(x: number): boolean { const idx = getBitPositionIndex(x); if (idx >= this.bits.length) return false; @@ -50,6 +116,10 @@ export class Bitmap { return (this.bits[idx] & mask) === mask; } + /** + * Count number of set bits in the bitmap (Hamming weight). + * @returns {number} Number of bits set to 1. + */ public count(): number { let count = 0; for (const byte of this.bits) { @@ -62,6 +132,12 @@ export class Bitmap { return count; } + /** + * Bitwise AND with another bitmap. Returns a new Bitmap containing the intersection. + * The result's capacity is equal to the left-hand bitmap's capacity (cloned from `this`). + * @param {Bitmap} bitmap - Other bitmap to AND with. + * @returns {Bitmap} New Bitmap representing this & bitmap. + */ public and(bitmap: Bitmap): Bitmap { const otherBits = (bitmap as Bitmap).bits; const result = this.clone() as Bitmap; @@ -78,6 +154,11 @@ export class Bitmap { return result; } + /** + * Bitwise AND NOT (this & ~bitmap). Returns a new Bitmap. + * @param {Bitmap} bitmap - Other bitmap to subtract from this. + * @returns {Bitmap} New Bitmap representing this & ~bitmap. + */ public andNot(bitmap: Bitmap): Bitmap { const otherBits = (bitmap as Bitmap).bits; const result = this.clone() as Bitmap; @@ -90,6 +171,12 @@ export class Bitmap { return result; } + /** + * Bitwise OR (union) with another bitmap. Returns a new Bitmap. + * The resulting bitmap will be large enough to contain bits from both operands. + * @param {Bitmap} bitmap - Other bitmap to OR with. + * @returns {Bitmap} New Bitmap representing this | bitmap. + */ public or(bitmap: Bitmap): Bitmap { const otherBits = (bitmap as Bitmap).bits; let result = this.clone() as Bitmap; @@ -110,6 +197,12 @@ export class Bitmap { return result; } + /** + * Bitwise XOR (symmetric difference) with another bitmap. Returns a new Bitmap. + * The resulting bitmap will be large enough to contain bits from both operands. + * @param {Bitmap} bitmap - Other bitmap to XOR with. + * @returns {Bitmap} New Bitmap representing this ^ bitmap. + */ public xor(bitmap: Bitmap): Bitmap { const otherBits = (bitmap as Bitmap).bits; let result = this.clone() as Bitmap; @@ -130,6 +223,12 @@ export class Bitmap { return result; } + /** + * Iterate over set bits and call `fn` for each set bit index in ascending order. + * If `fn` returns `false`, iteration stops early. + * @param {(x: number) => boolean | void} fn - Callback invoked with each set bit index. Returning `false` stops iteration. + * @returns {void} + */ public range(fn: (x: number) => boolean | void): void { let needContinueIterating = true; for (let i = 0; i < this.bits.length; i++) { @@ -144,6 +243,11 @@ export class Bitmap { } } + /** + * Remove bits for which `fn(bitIndex)` returns false. Mutates this bitmap. + * @param {(x: number) => boolean} fn - Predicate called for each set bit index; if it returns false the bit is cleared. + * @returns {void} + */ public filter(fn: (x: number) => boolean): void { for (let i = 0; i < this.bits.length; i++) { for (let j = 0; j < 8; j++) { @@ -155,18 +259,31 @@ export class Bitmap { } } + /** + * Clear all bits (set all to zero). + * @returns {void} + */ public clear(): void { for (let i = 0; i < this.bits.length; i++) { this.bits[i] = 0; } } + /** + * Create a deep copy of this bitmap. + * The clone's capacity is equal to the number of bits represented by the current byte-length. + * @returns {Bitmap} A new Bitmap instance with the same bits set. + */ public clone(): Bitmap { const clonedBitmap = new Bitmap(this.bits.length * 8); clonedBitmap.bits.set(this.bits); return clonedBitmap; } + /** + * Return the smallest set bit index or -1 if none are set. + * @returns {number} Minimum set bit index, or -1 if empty. + */ public min(): number { for (let i = 0; i < this.bits.length; i++) { const byte = this.bits[i]; @@ -180,6 +297,10 @@ export class Bitmap { return -1; } + /** + * Return the largest set bit index or -1 if none are set. + * @returns {number} Maximum set bit index, or -1 if empty. + */ public max(): number { let x = -1; for (let i = this.bits.length - 1; i >= 0; i--) { @@ -191,6 +312,10 @@ export class Bitmap { return x; } + /** + * Return the first zero bit index (smallest index not set). If all bits are set, returns capacity (bits.length * 8). + * @returns {number} Index of the first zero bit, or capacity if none found. + */ public minZero(): number { for (let i = 0; i < this.bits.length; i++) { const byte = this.bits[i]; @@ -204,6 +329,10 @@ export class Bitmap { return this.bits.length * 8; } + /** + * Return the last zero bit index (largest index not set) or -1 if all bits are set. + * @returns {number} Index of the last zero bit, or -1 if none found. + */ public maxZero(): number { for (let i = this.bits.length - 1; i >= 0; i--) { const byte = this.bits[i];