diff --git a/src/bitarray.ts b/src/bitarray.ts index 34d28a6..bdc6d99 100644 --- a/src/bitarray.ts +++ b/src/bitarray.ts @@ -1,7 +1,34 @@ import { join } from "./collector.js"; -import { arrayLike, sequence } from "./sync.js"; +import { Enumerable, arrayLike, sequence } from "./sync.js"; import { asArray, isDefined } from "./utils.js"; +export interface BitArray extends Iterable { + readonly length: number; + + isFull(): boolean; + isEmpty(): boolean; + + get(index: number): boolean; + set(index: number, value: boolean): void; + fill(value: boolean): void; + + and(other: BitArray): BitArray; + or(other: BitArray): BitArray; + xor(other: BitArray): BitArray; + not(): BitArray; + + contains(other: BitArray): boolean; + intersects(other: BitArray): boolean; + + slice(offset: number, length: number): BitArray; + copy(): BitArray; + toArray(): boolean[]; + + equals(other: BitArray): boolean; + + toString(): string; +} + const BYTE_SIZE = Uint8Array.BYTES_PER_ELEMENT * 8; const FULL_BYTE = getMask(BYTE_SIZE); @@ -54,7 +81,29 @@ function* getBytes(bits: Iterable) { } } -export class BitArray implements Iterable { +export namespace BitArray { + export function create(length: number): BitArray { + return new BitArrayImpl(length); + } + + export function from(bits: Iterable): BitArray { + const arr = asArray(bits); + const result = create(arr.length); + + let i = 0; + for (const bit of arr) { + result.set(i++, bit); + } + + return result; + } + + export function of(...bits: boolean[]): BitArray { + return from(bits); + } +} + +class BitArrayImpl implements BitArray { readonly #length: number; readonly #bits: Uint8Array; readonly #wholeBytes: number; @@ -72,22 +121,6 @@ export class BitArray implements Iterable { this.#remainingBitsMask = getMask(remainingBits); } - public static from(bits: Iterable) { - const arr = asArray(bits); - const result = new this(arr.length); - - let i = 0; - for (const bit of arr) { - result.set(i++, bit); - } - - return result; - } - - public static of(...bits: boolean[]) { - return this.from(bits); - } - *[Symbol.iterator]() { for (let i = 0; i < this.#wholeBytes; i++) { yield* yieldBits(this.#bits[i], BYTE_SIZE); @@ -149,8 +182,15 @@ export class BitArray implements Iterable { public and(other: BitArray) { this.#ensureSameSize(other); - for (let i = 0; i < this.#length; i++) { - this.#bits[i] &= other.#bits[i]; + if (other instanceof BitArrayImpl) { + for (let i = 0; i < this.#length; i++) { + this.#bits[i] &= other.#bits[i]; + } + } else { + let i = 0; + for (const byte of getBytes(other)) { + this.#bits[i++] &= byte; + } } return this; @@ -159,8 +199,15 @@ export class BitArray implements Iterable { public or(other: BitArray) { this.#ensureSameSize(other); - for (let i = 0; i < this.#length; i++) { - this.#bits[i] |= other.#bits[i]; + if (other instanceof BitArrayImpl) { + for (let i = 0; i < this.#length; i++) { + this.#bits[i] |= other.#bits[i]; + } + } else { + let i = 0; + for (const byte of getBytes(other)) { + this.#bits[i++] |= byte; + } } return this; @@ -169,8 +216,15 @@ export class BitArray implements Iterable { public xor(other: BitArray) { this.#ensureSameSize(other); - for (let i = 0; i < this.#length; i++) { - this.#bits[i] ^= other.#bits[i]; + if (other instanceof BitArrayImpl) { + for (let i = 0; i < this.#length; i++) { + this.#bits[i] ^= other.#bits[i]; + } + } else { + let i = 0; + for (const byte of getBytes(other)) { + this.#bits[i++] ^= byte; + } } return this; @@ -188,30 +242,28 @@ export class BitArray implements Iterable { return this; } - public andNot(other: BitArray) { - this.#ensureSameSize(other); - - for (let i = 0; i < this.#length; i++) { - this.#bits[i] &= ~other.#bits[i]; - } - - return this; - } - public contains(other: BitArray) { this.#ensureSameSize(other); - return arrayLike(this.#bits).zip(arrayLike(other.#bits)).all(([a, b]) => (a & b) === b); + return other instanceof BitArrayImpl ? + arrayLike(this.#bits).zip(arrayLike(other.#bits)).all(([a, b]) => (a & b) === b) : + sequence(this).zip(sequence(other)).where(([, b]) => b).all(([a, b]) => a && b); } public intersects(other: BitArray) { this.#ensureSameSize(other); - return arrayLike(this.#bits).zip(arrayLike(other.#bits)).any(([a, b]) => (a & b) !== 0); + return other instanceof BitArrayImpl ? + arrayLike(this.#bits).zip(arrayLike(other.#bits)).any(([a, b]) => (a & b) !== 0) : + sequence(this).zip(sequence(other)).any(([a, b]) => a && b); + } + + public slice(offset: number, length: number) { + return new BitArraySlice(this, offset, length); } public copy() { - const copy = new BitArray(this.#length); + const copy = new BitArrayImpl(this.#length); copy.#bits.set(this.#bits); return copy; } @@ -221,7 +273,155 @@ export class BitArray implements Iterable { } public equals(other: BitArray) { - return other === this || isDefined(other) && arrayLike(this.#bits).equals(arrayLike(other.#bits)); + return other === this || isDefined(other) && (other instanceof BitArrayImpl ? arrayLike(this.#bits).equals(arrayLike(other.#bits)) : sequence(this).equals(sequence(other))); + } + + public toString() { + return sequence(this).select(bit => bit ? '1' : '0').collect(join()); + } +} + +class BitArraySlice implements BitArray { + readonly #parent: BitArray; + readonly #offset: number; + readonly #length: number; + + public constructor(parent: BitArray, offset: number, length: number) { + this.#parent = parent; + this.#offset = offset; + this.#length = length; + } + + *[Symbol.iterator]() { + for (let i = 0; i < this.#length; i++) { + yield this.#parent.get(i + this.#offset); + } + } + + #ensureValidIndex(index: number) { + if (index < 0 || index >= this.#length) { + throw new RangeError("The index is outside the BitArray range."); + } + } + + #ensureSameSize(other: BitArray) { + if (this.#length !== other.length) { + throw new TypeError("The BitArrays do not have the same length."); + } + } + + public get length() { + return this.#length; + } + + public isFull() { + for (const bit of this) { + if (!bit) { + return false; + } + } + + return true; + } + + public isEmpty() { + for (const bit of this) { + if (bit) { + return false; + } + } + + return true; + } + + public get(index: number) { + this.#ensureValidIndex(index); + + return this.#parent.get(index + this.#offset); + } + + public set(index: number, value: boolean) { + this.#ensureValidIndex(index); + + this.#parent.set(index + this.#offset, value); + } + + public fill(value: boolean) { + for (let i = 0; i < this.#length; i++) { + this.#parent.set(i + this.#offset, value); + } + } + + public and(other: BitArray) { + this.#ensureSameSize(other); + + for (let i = 0; i < this.#length; i++) { + this.set(i, this.get(i) && other.get(i)); + } + + return this; + } + + public or(other: BitArray) { + this.#ensureSameSize(other); + + for (let i = 0; i < this.#length; i++) { + this.set(i, this.get(i) || other.get(i)); + } + + return this; + } + + public xor(other: BitArray) { + this.#ensureSameSize(other); + + for (let i = 0; i < this.#length; i++) { + this.set(i, this.get(i) !== other.get(i)); + } + + return this; + } + + public not() { + for (let i = 0; i < this.#length; i++) { + this.set(i, !this.get(i)); + } + + return this; + } + + public contains(other: BitArray) { + this.#ensureSameSize(other); + + return sequence(this).zip(sequence(other)).where(([, b]) => b).all(([a, b]) => a && b); + } + + public intersects(other: BitArray) { + this.#ensureSameSize(other); + + return sequence(this).zip(sequence(other)).any(([a, b]) => a && b); + } + + public slice(offset: number, length: number) { + return new BitArraySlice(this.#parent, offset + this.#offset, length); + } + + public copy() { + const copy = new BitArrayImpl(this.#length); + + for (let i = 0; i < this.#length; i++) { + copy.set(i, this.get(i)); + } + + return copy; + } + + public toArray() { + return sequence(this).toArray(); + } + + public equals(other: BitArray) { + return other === this || isDefined(other) && sequence(this).equals(sequence(other)); } public toString() { diff --git a/src/random.ts b/src/random.ts index 7ef6f13..fce31eb 100644 --- a/src/random.ts +++ b/src/random.ts @@ -82,6 +82,10 @@ export function getRandomElement(sequence: Iterable, options?: RandomOptio return { element: result, index: resultIndex }; } +export function getRandomElement(sequence: Iterable, options?: RandomOptions) { + return _getRandomElement(sequence, withDefaultOptions(options)); +} + export class RandomPicker { readonly #elements: Iterable; readonly #flags: BitArray; @@ -89,7 +93,7 @@ export class RandomPicker { public constructor(elements: T[], options?: RandomOptions) { this.#elements = elements; - this.#flags = new BitArray(elements.length); + this.#flags = BitArray.create(elements.length); this.#options = withDefaultOptions(mergeOptions({ predicate: i => this.#flags.get(i) }, options)); this.reset();