refactor(bitmap): switch to mutating API and return `this` from all setters

The Bitmap API is now fully mutating:
- all state-changing methods (`set`, `remove`, `grow`, `clear`, `filter`) return `this`
- bitwise operations (`and`, `or`, `xor`, `andNot`) no longer clone internally and now mutate the current bitmap
- functional (immutable-style) usage is still possible via explicit `.clone()`
- removed implicit cloning from all arithmetic operations

This change makes the mutation semantics consistent and predictable, while keeping optional immutability available through manual cloning.
This commit is contained in:
Eugene 2025-12-10 16:01:04 +03:00
parent 21761e5dac
commit 594ec641a4
1 changed files with 81 additions and 85 deletions

View File

@ -123,27 +123,30 @@ export class Bitmap {
/** /**
* Set the bit at index `x` to 1. * Set the bit at index `x` to 1.
* Automatically grows the internal buffer if necessary. * Automatically grows the bitmap if necessary.
* @param {number} x - Bit index to set (0-based). * **Mutates this bitmap** in-place.
* @returns {void} * @param {number} x - Bit index (0-based).
* @returns {this} The instance itself (for method chaining).
*/ */
public set(x: number): void { public set(x: number): this {
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));
return this;
} }
/** /**
* Clear the bit at index `x` (set to 0). * Clear the bit at index `x` (set to 0).
* If `x` is beyond current capacity, the call is a no-op. * No-op if the index is outside current capacity.
* @param {number} x - Bit index to clear (0-based). * **Mutates this bitmap** in-place.
* @returns {void} * @param {number} x - Bit index (0-based).
* @returns {this} The instance itself (for method chaining).
*/ */
public remove(x: number): void { public remove(x: number): this {
const idx = getBitPositionIndex(x); const idx = getBitPositionIndex(x);
if (idx >= this.bits.length) return; if (idx >= this.bits.length) return this;
const mask = getMask(getBitPosition(x)); this.bits[idx] &= ~getMask(getBitPosition(x));
this.bits[idx] &= ~mask; return this;
} }
/** /**
@ -175,94 +178,85 @@ export class Bitmap {
} }
/** /**
* Bitwise AND with another bitmap. Returns a new Bitmap containing the intersection. * Bitwise AND with another bitmap.
* The result's capacity is equal to the left-hand bitmap's capacity (cloned from `this`). * **Mutates this bitmap** performs the operation in-place and clears extra bytes if needed.
* @param {Bitmap} bitmap - Other bitmap to AND with. * @param {Bitmap} bitmap - Other bitmap to AND with.
* @returns {Bitmap} New Bitmap representing this & bitmap. * @returns {this} The instance itself (for method chaining).
*/ */
public and(bitmap: Bitmap): Bitmap { public and(bitmap: Bitmap): this {
const otherBits = (bitmap as Bitmap).bits; const other = bitmap.bits;
const result = this.clone() as Bitmap; const minlen = Math.min(this.bits.length, other.length);
const minlen = Math.min(this.bits.length, otherBits.length);
for (let i = 0; i < minlen; i++) { for (let i = 0; i < minlen; i++) {
result.bits[i] &= otherBits[i]; this.bits[i] &= other[i];
} }
for (let i = minlen; i < this.bits.length; i++) {
for (let i = minlen; i < result.bits.length; i++) { this.bits[i] = 0;
result.bits[i] = 0;
} }
return this;
return result;
} }
/** /**
* Bitwise AND NOT (this & ~bitmap). Returns a new Bitmap. * Bitwise AND NOT (this = this & ~bitmap).
* @param {Bitmap} bitmap - Other bitmap to subtract from this. * **Mutates this bitmap** in-place.
* @returns {Bitmap} New Bitmap representing this & ~bitmap. * @param {Bitmap} bitmap - Bitmap whose set bits will be subtracted.
* @returns {this} The instance itself (for method chaining).
*/ */
public andNot(bitmap: Bitmap): Bitmap { public andNot(bitmap: Bitmap): this {
const otherBits = (bitmap as Bitmap).bits; const other = bitmap.bits;
const result = this.clone() as Bitmap; const minlen = Math.min(this.bits.length, other.length);
const minlen = Math.min(this.bits.length, otherBits.length);
for (let i = 0; i < minlen; i++) { for (let i = 0; i < minlen; i++) {
result.bits[i] &= ~otherBits[i]; this.bits[i] &= ~other[i];
} }
return this;
return result;
} }
/** /**
* Bitwise OR (union) with another bitmap. Returns a new Bitmap. * Bitwise OR (union) with another bitmap.
* The resulting bitmap will be large enough to contain bits from both operands. * **Mutates this bitmap** in-place and grows it if necessary.
* @param {Bitmap} bitmap - Other bitmap to OR with. * @param {Bitmap} bitmap - Other bitmap to OR with.
* @returns {Bitmap} New Bitmap representing this | bitmap. * @returns {this} The instance itself (for method chaining).
*/ */
public or(bitmap: Bitmap): Bitmap { public or(bitmap: Bitmap): this {
const otherBits = (bitmap as Bitmap).bits; const other = bitmap.bits;
let result = this.clone() as Bitmap;
if (otherBits.length > result.bits.length) { if (other.length > this.bits.length) {
const extended = new Uint8Array(otherBits.length); const newArr = new Uint8Array(other.length);
extended.set(result.bits); newArr.set(this.bits);
result.bits = extended; this.bits = newArr;
} }
const maxlen = Math.max(result.bits.length, otherBits.length); const maxlen = Math.max(this.bits.length, other.length);
for (let i = 0; i < maxlen; i++) { for (let i = 0; i < maxlen; i++) {
const a = result.bits[i] ?? 0; const a = this.bits[i] ?? 0;
const b = otherBits[i] ?? 0; const b = other[i] ?? 0;
result.bits[i] = a | b; this.bits[i] = a | b;
} }
return this;
return result;
} }
/** /**
* Bitwise XOR (symmetric difference) with another bitmap. Returns a new Bitmap. * Bitwise XOR with another bitmap.
* The resulting bitmap will be large enough to contain bits from both operands. * **Mutates this bitmap** in-place and grows it if necessary.
* @param {Bitmap} bitmap - Other bitmap to XOR with. * @param {Bitmap} bitmap - Other bitmap to XOR with.
* @returns {Bitmap} New Bitmap representing this ^ bitmap. * @returns {this} The instance itself (for method chaining).
*/ */
public xor(bitmap: Bitmap): Bitmap { public xor(bitmap: Bitmap): this {
const otherBits = (bitmap as Bitmap).bits; const other = bitmap.bits;
let result = this.clone() as Bitmap;
if (otherBits.length > result.bits.length) { if (other.length > this.bits.length) {
const extended = new Uint8Array(otherBits.length); const newArr = new Uint8Array(other.length);
extended.set(result.bits); newArr.set(this.bits);
result.bits = extended; this.bits = newArr;
} }
const maxlen = Math.max(result.bits.length, otherBits.length); const maxlen = Math.max(this.bits.length, other.length);
for (let i = 0; i < maxlen; i++) { for (let i = 0; i < maxlen; i++) {
const a = result.bits[i] ?? 0; const a = this.bits[i] ?? 0;
const b = otherBits[i] ?? 0; const b = other[i] ?? 0;
result.bits[i] = a ^ b; this.bits[i] = a ^ b;
} }
return this;
return result;
} }
/** /**
@ -286,8 +280,9 @@ export class Bitmap {
} }
/** /**
* Remove bits for which `fn(bitIndex)` returns false. Mutates this bitmap. * Remove bits for which the predicate returns `false`.
* @param {(x: number) => boolean} fn - Predicate called for each set bit index; if it returns false the bit is cleared. * **Mutates this bitmap** in-place.
* @param {(bitIndex: number) => boolean} fn - Predicate; bit is cleared if `false` is returned.
* @returns {void} * @returns {void}
*/ */
public filter(fn: (x: number) => boolean): void { public filter(fn: (x: number) => boolean): void {
@ -302,7 +297,8 @@ export class Bitmap {
} }
/** /**
* Clear all bits (set all to zero). * Clear all bits in the bitmap (set everything to 0).
* **Mutates this bitmap** in-place.
* @returns {void} * @returns {void}
*/ */
public clear(): void { public clear(): void {