docs(bitmap): add JSDoc for Bitmap class and helper functions

Add comprehensive JSDoc comments to src/bitmap.ts covering:
- private helper functions (computeBitsArrayLength, getBitPositionIndex, getBitPosition, getMask)
- the Bitmap class and all public methods (constructor, iterator, grow, set, remove, contains, count, and, andNot, or, xor, range, filter, clear, clone, min, max, minZero, maxZero)

Comments describe parameters, return values and behavior (including iteration semantics). No runtime behaviour changes — this improves editor/tooling support (autocomplete/type hints) and prepares the code for TypeDoc generation.
This commit is contained in:
Eugene 2025-12-01 22:45:36 +03:00
parent 6e845f5543
commit 5667aef80f
1 changed files with 129 additions and 0 deletions

View File

@ -1,15 +1,58 @@
const computeBitsArrayLength = (x: number): number => Math.ceil(x / 8); 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); 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; 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; const getMask = (bitIdx: number): number => 0b00000001 << bitIdx;
export class Bitmap { export class Bitmap {
private bits: Uint8Array; 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) { constructor(x = 32) {
this.bits = new Uint8Array(computeBitsArrayLength(x)); 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<number> { public *[Symbol.iterator](): Generator<number> {
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
const byte = this.bits[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 { public grow(x: number): void {
const arrLength = Math.max(1, computeBitsArrayLength(x + 1)); const arrLength = Math.max(1, computeBitsArrayLength(x + 1));
if (arrLength <= this.bits.length) return; if (arrLength <= this.bits.length) return;
@ -30,12 +79,24 @@ export class Bitmap {
this.bits.set(prev); 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 { public set(x: number): void {
const idx = getBitPositionIndex(x); const idx = getBitPositionIndex(x);
if (idx >= this.bits.length) this.grow(x); if (idx >= this.bits.length) this.grow(x);
this.bits[idx] |= getMask(getBitPosition(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 { public remove(x: number): void {
const idx = getBitPositionIndex(x); const idx = getBitPositionIndex(x);
if (idx >= this.bits.length) return; if (idx >= this.bits.length) return;
@ -43,6 +104,11 @@ export class Bitmap {
this.bits[idx] &= ~mask; 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 { public contains(x: number): boolean {
const idx = getBitPositionIndex(x); const idx = getBitPositionIndex(x);
if (idx >= this.bits.length) return false; if (idx >= this.bits.length) return false;
@ -50,6 +116,10 @@ export class Bitmap {
return (this.bits[idx] & mask) === mask; 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 { public count(): number {
let count = 0; let count = 0;
for (const byte of this.bits) { for (const byte of this.bits) {
@ -62,6 +132,12 @@ export class Bitmap {
return count; 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 { public and(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
const result = this.clone() as Bitmap; const result = this.clone() as Bitmap;
@ -78,6 +154,11 @@ export class Bitmap {
return result; 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 { public andNot(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
const result = this.clone() as Bitmap; const result = this.clone() as Bitmap;
@ -90,6 +171,12 @@ export class Bitmap {
return result; 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 { public or(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
let result = this.clone() as Bitmap; let result = this.clone() as Bitmap;
@ -110,6 +197,12 @@ export class Bitmap {
return result; 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 { public xor(bitmap: Bitmap): Bitmap {
const otherBits = (bitmap as Bitmap).bits; const otherBits = (bitmap as Bitmap).bits;
let result = this.clone() as Bitmap; let result = this.clone() as Bitmap;
@ -130,6 +223,12 @@ export class Bitmap {
return result; 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 { public range(fn: (x: number) => boolean | void): void {
let needContinueIterating = true; let needContinueIterating = true;
for (let i = 0; i < this.bits.length; i++) { 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 { public filter(fn: (x: number) => boolean): void {
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
for (let j = 0; j < 8; j++) { 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 { public clear(): void {
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
this.bits[i] = 0; 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 { public clone(): Bitmap {
const clonedBitmap = new Bitmap(this.bits.length * 8); const clonedBitmap = new Bitmap(this.bits.length * 8);
clonedBitmap.bits.set(this.bits); clonedBitmap.bits.set(this.bits);
return clonedBitmap; 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 { public min(): number {
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
const byte = this.bits[i]; const byte = this.bits[i];
@ -180,6 +297,10 @@ export class Bitmap {
return -1; 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 { public max(): number {
let x = -1; let x = -1;
for (let i = this.bits.length - 1; i >= 0; i--) { for (let i = this.bits.length - 1; i >= 0; i--) {
@ -191,6 +312,10 @@ export class Bitmap {
return x; 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 { public minZero(): number {
for (let i = 0; i < this.bits.length; i++) { for (let i = 0; i < this.bits.length; i++) {
const byte = this.bits[i]; const byte = this.bits[i];
@ -204,6 +329,10 @@ export class Bitmap {
return this.bits.length * 8; 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 { public maxZero(): number {
for (let i = this.bits.length - 1; i >= 0; i--) { for (let i = this.bits.length - 1; i >= 0; i--) {
const byte = this.bits[i]; const byte = this.bits[i];