/** * @module * * MeekSet data collection. */ /** * Private data. * * @template T Value type. */ interface Pri { /** * Finalization registry. */ readonly fr: FinalizationRegistry>; /** * Map of values to weak references. */ vwv: WeakMap>; /** * Set of weak references to values. */ readonly wv: Set>; } const pri = new WeakMap(); /** * Like WeakSet. * * @template T Value type. */ export class MeekSet { /** * Type string. */ declare public readonly [Symbol.toStringTag]: string; /** * Create a new MeekSet. * * @param iterable Initial values. */ constructor(iterable?: Iterable | null) { const vwv = new WeakMap>(); const wv = new Set>(); const fr = new FinalizationRegistry(wv.delete.bind(wv)); for (const value of iterable ?? []) { if (!vwv.has(value)) { const ref = new WeakRef(value); fr.register(value, ref, value); vwv.set(value, ref); wv.add(ref); } } pri.set(this, { fr, vwv, wv }); } /** * Iterator for values in this set. * * @returns Set iterator. */ public *[Symbol.iterator](): SetIterator { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { yield value; } } } /** * Add a value to this set. * * @param value Value to add. * @returns This set. */ public add(value: T): this { const { fr, vwv, wv } = pri.get(this) as Pri; let ref = vwv.get(value); if (!ref) { ref = new WeakRef(value); fr.register(value, ref, value); vwv.set(value, ref); wv.add(ref); } return this; } /** * Clear this set. */ public clear(): void { const p = pri.get(this) as Pri; const map = new WeakMap>(); p.wv.clear(); p.vwv = map; } /** * Delete a value from this set. * * @param value Value to delete. * @returns Whether the value was deleted. */ public delete(value: T): boolean { const { fr, vwv, wv } = pri.get(this) as Pri; const ref = vwv.get(value); if (ref) { fr.unregister(value); vwv.delete(value); return wv.delete(ref); } return false; } /** * New MeekSet containing the values in this set not in other set. * * @param other Other set. * @returns New MeekSet. */ public difference(other: ReadonlySetLike): MeekSet { const set = new MeekSet(); for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value && !other.has(value as unknown as U)) { set.add(value); } } return set; } /** * Iterator for key-value pairs in this set. * * @returns Key-value iterator. */ public *entries(): SetIterator<[T, T]> { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { yield [value, value]; } } } /** * Call a function for each value in this set. * * @param callbackfn Callback function. * @param thisArg This argument. */ public forEach( callbackfn: (value: T, value2: T, set: MeekSet) => void, thisArg?: any, ): void { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { callbackfn.call(thisArg, value, value, this); } } } /** * Has a value in this set. * * @param value Value to check. * @returns Whether the value is in this set. */ public has(value: T): boolean { return (pri.get(this) as Pri).vwv.has(value); } /** * New MeekSet containing the values in both sets. * * @param other Other set. * @returns New MeekSet. */ public intersection( other: ReadonlySetLike, ): MeekSet { const set = new MeekSet(); for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref() as T & U; if (value) { if (other.has(value)) { set.add(value); } } } return set; } /** * Is every value in this set not in other set. * * @param other Other set. * @returns Whether every value in this set is not in other set. */ public isDisjointFrom(other: ReadonlySetLike): boolean { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value && other.has(value)) { return false; } } return true; } /** * Is every value in this set in other set. * * @param other Other set. * @returns Whether every value in this set is in other set. */ public isSubsetOf(other: ReadonlySetLike): boolean { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value && !other.has(value)) { return false; } } return true; } /** * Is every value in other set in this set. * * @param other Other set. * @returns Whether every value in other set is in this set. */ public isSupersetOf(other: ReadonlySetLike): boolean { const p = pri.get(this) as Pri; const it = other.keys(); for (let result = it.next(); !result.done; result = it.next()) { const { value } = result as { value: T }; if (!p.vwv.has(value)) { return false; } } return true; } /** * Iterator for keys in this set. * * @returns Key iterator. */ public *keys(): SetIterator { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { yield value; } } } /** * The number of values in this set. * Can be greater than number of active keys. */ public get size(): number { return (pri.get(this) as Pri).wv.size; } /** * New MeekSet containing the values in either set but not both. * * @param other Other set. * @returns New MeekSet. */ public symmetricDifference( other: ReadonlySetLike, ): MeekSet { const p = pri.get(this) as Pri; const set = new MeekSet(); for (const ref of p.wv) { const value = ref.deref(); if (value && !other.has(value as unknown as U)) { set.add(value); } } const it = other.keys(); for (let result = it.next(); !result.done; result = it.next()) { const { value } = result as { value: T & U }; if (!p.vwv.has(value)) { set.add(value); } } return set; } /** * New MeekSet containing all values from both sets. * * @param other Other set. * @returns New MeekSet. */ public union(other: ReadonlySetLike): MeekSet { const set = new MeekSet(); for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { set.add(value); } } const it = other.keys(); for (let result = it.next(); !result.done; result = it.next()) { set.add(result.value); } return set; } /** * Iterator for values in this set. * * @returns Value iterator. */ public *values(): SetIterator { for (const ref of (pri.get(this) as Pri).wv) { const value = ref.deref(); if (value) { yield value; } } } static { Object.defineProperty(this.prototype, Symbol.toStringTag, { value: 'MeekSet', configurable: true, enumerable: false, writable: false, }); } } /** * Readonly MeekSet. * * @template T Value type. */ export type ReadonlyMeekSet = Omit< MeekSet, 'add' | 'clear' | 'delete' >;