red-black-tree-typed
Version:
5,357 lines (777 loc) • 45 kB
text/typescript
/**
* TreeSet (ordered set) — a restricted, native-like API backed by RedBlackTree.
*
* Design goals:
* - No node exposure (no node inputs/outputs)
* - Native Set-like surface + Java NavigableSet-like helpers
* - Strict default comparator (number/string/Date), otherwise require comparator
*/
import type { Comparator } from '../../types';
import type { TreeSetElementCallback, TreeSetOptions, TreeSetRangeOptions, TreeSetReduceCallback } from '../../types';
import { ERR, raise } from '../../common';
import { RedBlackTree } from './red-black-tree';
/**
* An ordered Set backed by a red-black tree.
*
* - Iteration order is ascending by key.
* - No node exposure: all APIs use keys only.
* @example
* // Set multiple key-value pairs
* const ts = new TreeSet<number, string>();
* ts.setMany([[1, 'a'], [2, 'b'], [3, 'c']]);
* console.log(ts.size); // 3;
*/
export class TreeSet<K = any, R = K> implements Iterable<K> {
readonly #core: RedBlackTree<K, undefined>;
readonly #isDefaultComparator: boolean;
readonly #userComparator?: Comparator<K>;
/**
* Create a TreeSet from an iterable of keys or raw elements.
*
* @param elements - Iterable of keys, or raw elements if `toElementFn` is provided.
* @param options - Configuration options including optional `toElementFn` to transform raw elements.
* @throws {TypeError} When using the default comparator and encountering unsupported key types,
* or invalid keys (e.g. `NaN`, invalid `Date`).
* @example
* // Standard usage with keys
* const set = new TreeSet([3, 1, 2]);
*
* // Using toElementFn to transform raw objects
* const users = [{ id: 3, name: 'Alice' }, { id: 1, name: 'Bob' }];
* const set = new TreeSet<number, User>(users, { toElementFn: u => u.id });
*/
constructor(elements: Iterable<R> | Iterable<K> = [], options: TreeSetOptions<K, R> = {}) {
this.#userComparator = options.comparator;
const toElementFn = options.toElementFn;
const comparator = options.comparator ?? TreeSet.createDefaultComparator<K>();
this.#isDefaultComparator = options.comparator === undefined;
// RedBlackTree expects an iterable of keys/entries/nodes/raws; for TreeSet we only accept keys.
this.#core = new RedBlackTree<K, undefined>([], { comparator, isMapMode: options.isMapMode, enableOrderStatistic: options.enableOrderStatistic });
for (const item of elements) {
const k = toElementFn ? toElementFn(item as R) : item as K;
this.add(k);
}
}
/**
* Create the strict default comparator.
*
* Supports:
* - `number` (rejects `NaN`; treats `-0` and `0` as equal)
* - `string`
* - `Date` (orders by `getTime()`, rejects invalid dates)
*
* For other key types, a custom comparator must be provided.
*/
static createDefaultComparator<K>(): Comparator<K> {
return (a: K, b: K): number => {
// numbers
if (typeof a === 'number' && typeof b === 'number') {
/* istanbul ignore next -- _validateKey prevents NaN from entering the tree */
if (Number.isNaN(a) || Number.isNaN(b)) raise(TypeError, ERR.invalidNaN('TreeSet'));
const aa = Object.is(a, -0) ? 0 : a;
const bb = Object.is(b, -0) ? 0 : b;
return aa > bb ? 1 : aa < bb ? -1 : 0;
}
if (typeof a === 'string' && typeof b === 'string') {
return a > b ? 1 : a < b ? -1 : 0;
}
if (a instanceof Date && b instanceof Date) {
const ta = a.getTime();
const tb = b.getTime();
/* istanbul ignore next -- _validateKey prevents invalid Date from entering the tree */
if (Number.isNaN(ta) || Number.isNaN(tb)) raise(TypeError, ERR.invalidDate('TreeSet'));
return ta > tb ? 1 : ta < tb ? -1 : 0;
}
raise(TypeError, ERR.comparatorRequired('TreeSet'));
};
}
/**
* Number of elements in the set.
*/
get size(): number {
return this.#core.size;
}
/**
* Whether the set is empty.
* @example
* // Check empty
* console.log(new TreeSet().isEmpty()); // true;
*/
isEmpty(): boolean {
return this.size === 0;
}
private _validateKey(key: K): void {
if (!this.#isDefaultComparator) return;
if (typeof key === 'number') {
if (Number.isNaN(key)) raise(TypeError, ERR.invalidNaN('TreeSet'));
return;
}
if (typeof key === 'string') return;
if (key instanceof Date) {
if (Number.isNaN(key.getTime())) raise(TypeError, ERR.invalidDate('TreeSet'));
return;
}
// Other key types should have provided a comparator, so reaching here means misuse.
raise(TypeError, ERR.comparatorRequired('TreeSet'));
}
/**
* Add a key to the set (no-op if already present).
* @remarks Expected time O(log n)
* @example
* // Unique tags with sorted order
* const tags = new TreeSet<string>(['javascript', 'typescript', 'react', 'typescript', 'node']);
*
* // Duplicates removed, sorted alphabetically
* console.log([...tags]); // ['javascript', 'node', 'react', 'typescript'];
* console.log(tags.size); // 4;
*
* tags.add('angular');
* console.log(tags.first()); // 'angular';
* console.log(tags.last()); // 'typescript';
*/
add(key: K): this {
this._validateKey(key);
// RBT.set returns boolean; Set.add returns this.
this.#core.set(key, undefined);
return this;
}
/**
* Add multiple keys at once.
* @remarks Expected time O(m log n), where m is the number of keys.
* @param keys - Iterable of keys to add.
* @returns Array of booleans indicating whether each key was newly added.
* @example
* // Add multiple keys
* const ts = new TreeSet<number>();
* ts.addMany([5, 3, 7, 1, 9]);
* console.log(ts.size); // 5;
*/
addMany(keys: Iterable<K>): boolean[] {
const results: boolean[] = [];
for (const key of keys) {
this._validateKey(key);
results.push(this.#core.set(key, undefined));
}
return results;
}
/**
* Test whether a key exists.
* @remarks Expected time O(log n)
* @example
* // Checking membership in a sorted collection
* const allowed = new TreeSet<string>(['admin', 'editor', 'viewer']);
*
* console.log(allowed.has('admin')); // true;
* console.log(allowed.has('guest')); // false;
*/
has(key: K): boolean {
this._validateKey(key);
return this.#core.has(key);
}
/**
* Delete a key.
* @returns `true` if the key existed; otherwise `false`.
* @remarks Expected time O(log n)
* @example
* // Removing elements while maintaining order
* const nums = new TreeSet<number>([1, 3, 5, 7, 9]);
*
* console.log(nums.delete(5)); // true;
* console.log(nums.delete(5)); // false; // already gone
* console.log([...nums]); // [1, 3, 7, 9];
*/
delete(key: K): boolean {
this._validateKey(key);
return this.#core.delete(key);
}
/**
* Delete all keys matching a predicate.
* @remarks Time O(N), Space O(N)
* @param predicate - Function (key, index, set) → boolean; return true to delete.
* @returns True if at least one key was deleted.
*/
deleteWhere(predicate: (key: K, index: number, set: this) => boolean): boolean {
let deleted = false;
let index = 0;
for (const key of this) {
if (predicate(key, index++, this)) {
this.delete(key);
deleted = true;
}
}
return deleted;
}
/**
* Remove all keys.
* @example
* // Remove all
* const ts = new TreeSet<number>([1, 2]);
* ts.clear();
* console.log(ts.isEmpty()); // true;
*/
clear(): void {
this.#core.clear();
}
/**
* Iterate over keys in ascending order.
* @example
* // Get sorted keys
* const ts = new TreeSet<number>([30, 10, 20]);
* console.log([...ts.keys()]); // [10, 20, 30];
*/
keys(): IterableIterator<K> {
return this.#core.keys();
}
/**
* Iterate over values in ascending order.
*
* Note: for Set-like containers, `values()` is the same as `keys()`.
* @example
* // Get values (same as keys for Set)
* const ts = new TreeSet<number>([2, 1, 3]);
* console.log([...ts.values()]); // [1, 2, 3];
*/
values(): IterableIterator<K> {
return this.keys();
}
/**
* Iterate over `[value, value]` pairs (native Set convention).
*
* Note: TreeSet stores only keys internally; `[k, k]` is created on-the-fly during iteration.
* @example
* // Iterate entries
* const ts = new TreeSet<number>([3, 1, 2]);
* console.log([...ts.entries()].map(([k]) => k)); // [1, 2, 3];
*/
*entries(): IterableIterator<[K, K]> {
for (const k of this.keys()) yield [k, k];
}
[Symbol.iterator](): IterableIterator<K> {
return this.keys();
}
/**
* Visit each value in ascending order.
*
* Callback follows native Set convention: `(value, value2, set)`.
* @example
* // Execute for each
* const ts = new TreeSet<number>([3, 1, 2]);
* const keys: number[] = [];
* ts.forEach(k => keys.push(k));
* console.log(keys); // [1, 2, 3];
*/
forEach(cb: (value: K, value2: K, set: TreeSet<K>) => void, thisArg?: unknown): void {
for (const k of this) cb.call(thisArg, k, k, this);
}
/**
* Create a new TreeSet by mapping each value to a new key.
*
* This mirrors `RedBlackTree.map`: mapping produces a new ordered container.
* @remarks Time O(n log n) expected, Space O(n)
* @example
* // Transform
* const ts = new TreeSet<number>([1, 2, 3]);
* const doubled = ts.map(k => k * 2);
* console.log([...doubled]); // [2, 4, 6];
*/
map<MK>(
callbackfn: TreeSetElementCallback<K, MK, TreeSet<K>>,
options: Omit<TreeSetOptions<MK>, 'toElementFn'> & { comparator?: (a: MK, b: MK) => number } = {},
thisArg?: unknown
): TreeSet<MK> {
const out = new TreeSet<MK>([], options as TreeSetOptions<MK>);
let index = 0;
for (const v of this) {
const mk = thisArg === undefined
? callbackfn(v, index++, this)
: (callbackfn as (this: unknown, v: K, i: number, self: TreeSet<K>) => MK).call(thisArg, v, index++, this);
out.add(mk);
}
return out;
}
/**
* Create a new TreeSet containing only values that satisfy the predicate.
* @remarks Time O(n log n) expected, Space O(n)
* @example
* // Filter
* const ts = new TreeSet<number>([1, 2, 3, 4, 5]);
* const evens = ts.filter(k => k % 2 === 0);
* console.log([...evens]); // [2, 4];
*/
filter(callbackfn: TreeSetElementCallback<K, boolean, TreeSet<K>>, thisArg?: unknown): TreeSet<K> {
const out = new TreeSet<K>([], { comparator: this.#userComparator });
let index = 0;
for (const v of this) {
const ok = thisArg === undefined
? callbackfn(v, index++, this)
: (callbackfn as (this: unknown, v: K, i: number, self: TreeSet<K>) => boolean).call(thisArg, v, index++, this);
if (ok) out.add(v);
}
return out;
}
/**
* Reduce values into a single accumulator.
* @remarks Time O(n), Space O(1)
* @example
* // Aggregate
* const ts = new TreeSet<number>([1, 2, 3]);
* const sum = ts.reduce((acc, k) => acc + k, 0);
* console.log(sum); // 6;
*/
reduce<A>(callbackfn: TreeSetReduceCallback<K, A, TreeSet<K>>, initialValue: A): A {
let acc = initialValue;
let index = 0;
for (const v of this) acc = callbackfn(acc, v, index++, this);
return acc;
}
/**
* Test whether all values satisfy a predicate.
* @remarks Time O(n), Space O(1)
* @example
* // Test all
* const ts = new TreeSet<number>([2, 4, 6]);
* console.log(ts.every(k => k > 0)); // true;
*/
every(callbackfn: TreeSetElementCallback<K, boolean, TreeSet<K>>, thisArg?: unknown): boolean {
let index = 0;
for (const v of this) {
const ok = thisArg === undefined
? callbackfn(v, index++, this)
: (callbackfn as (this: unknown, v: K, i: number, self: TreeSet<K>) => boolean).call(thisArg, v, index++, this);
if (!ok) return false;
}
return true;
}
/**
* Test whether any value satisfies a predicate.
* @remarks Time O(n), Space O(1)
* @example
* // Test any
* const ts = new TreeSet<number>([1, 3, 5]);
* console.log(ts.some(k => k === 3)); // true;
*/
some(callbackfn: TreeSetElementCallback<K, boolean, TreeSet<K>>, thisArg?: unknown): boolean {
let index = 0;
for (const v of this) {
const ok = thisArg === undefined
? callbackfn(v, index++, this)
: (callbackfn as (this: unknown, v: K, i: number, self: TreeSet<K>) => boolean).call(thisArg, v, index++, this);
if (ok) return true;
}
return false;
}
/**
* Find the first value that satisfies a predicate.
* @remarks Time O(n), Space O(1)
* @example
* // Find entry
* const ts = new TreeSet<number>([1, 2, 3]);
* const found = ts.find(k => k === 2);
* console.log(found); // 2;
*/
find(callbackfn: TreeSetElementCallback<K, boolean, TreeSet<K>>, thisArg?: unknown): K | undefined {
let index = 0;
for (const v of this) {
const ok = thisArg === undefined
? callbackfn(v, index++, this)
: (callbackfn as (this: unknown, v: K, i: number, self: TreeSet<K>) => boolean).call(thisArg, v, index++, this);
if (ok) return v;
}
return undefined;
}
/**
* Materialize the set into an array of keys.
* @remarks Time O(n), Space O(n)
* @example
* // Convert to array
* const ts = new TreeSet<number>([3, 1, 2]);
* console.log(ts.toArray()); // [1, 2, 3];
*/
toArray(): K[] {
return [...this];
}
/**
* Print a human-friendly representation.
* @remarks Time O(n), Space O(n)
* @example
* // Display tree
* const ts = new TreeSet<number>([1, 2, 3]);
* expect(() => ts.print()).not.toThrow();
*/
print(): void {
// Delegate to the underlying tree's visualization.
this.#core.print();
}
// Navigable operations
/**
* Smallest key in the set.
* @example
* // Student grade ranking with custom comparator
* interface Student {
* name: string;
* gpa: number;
* }
*
* const ranking = new TreeSet<Student>(
* [
* { name: 'Alice', gpa: 3.8 },
* { name: 'Bob', gpa: 3.5 },
* { name: 'Charlie', gpa: 3.9 },
* { name: 'Diana', gpa: 3.5 }
* ],
* { comparator: (a, b) => b.gpa - a.gpa || a.name.localeCompare(b.name) }
* );
*
* // Sorted by GPA descending, then name ascending
* const names = [...ranking].map(s => s.name);
* console.log(names); // ['Charlie', 'Alice', 'Bob', 'Diana'];
*
* // Top student
* console.log(ranking.first()?.name); // 'Charlie';
*
* // Filter students with GPA >= 3.8
* const honors = ranking.filter(s => s.gpa >= 3.8);
* console.log(honors.toArray().map(s => s.name)); // ['Charlie', 'Alice'];
*/
first(): K | undefined {
return this.#core.getLeftMost();
}
/**
* Largest key in the set.
* @example
* // Get the maximum element
* const temps = new TreeSet<number>([18, 22, 15, 30, 25]);
* console.log(temps.last()); // 30;
* console.log(temps.first()); // 15;
*/
last(): K | undefined {
return this.#core.getRightMost();
}
/**
* Remove and return the smallest key.
* @example
* // Remove and return minimum
* const queue = new TreeSet<number>([5, 1, 8, 3]);
*
* console.log(queue.pollFirst()); // 1;
* console.log(queue.pollFirst()); // 3;
* console.log(queue.size); // 2;
*/
pollFirst(): K | undefined {
const k = this.first();
if (k === undefined) return undefined;
this.delete(k);
return k;
}
/**
* Remove and return the largest key.
* @example
* // Remove and return maximum
* const stack = new TreeSet<number>([10, 20, 30]);
*
* console.log(stack.pollLast()); // 30;
* console.log(stack.size); // 2;
*/
pollLast(): K | undefined {
const k = this.last();
if (k === undefined) return undefined;
this.delete(k);
return k;
}
/**
* Smallest key that is >= the given key.
* @example
* // Finding nearest available time slot
* // Available appointment times (minutes from midnight)
* const slots = new TreeSet<number>([540, 600, 660, 720, 840, 900]);
*
* // Customer wants something around 10:30 (630 min)
* const nearest = slots.ceiling(630);
* console.log(nearest); // 660; // 11:00 AM
*
* // What's the latest slot before 2:00 PM (840)?
* const before2pm = slots.lower(840);
* console.log(before2pm); // 720; // 12:00 PM
*
* // Book the 11:00 slot
* slots.delete(660);
* console.log(slots.ceiling(630)); // 720;
*/
ceiling(key: K): K | undefined {
this._validateKey(key);
return this.#core.ceiling(key);
}
/**
* Largest key that is <= the given key.
* @example
* // Largest element ≤ target
* const breakpoints = new TreeSet<number>([320, 768, 1024, 1280, 1920]);
*
* // Current width is 800 → which breakpoint applies?
* console.log(breakpoints.floor(800)); // 768;
* console.log(breakpoints.floor(1024)); // 1024; // exact match
* console.log(breakpoints.floor(100)); // undefined;
*/
floor(key: K): K | undefined {
this._validateKey(key);
return this.#core.floor(key);
}
/**
* Smallest key that is > the given key.
* @example
* // Smallest element strictly > target
* const levels = new TreeSet<number>([1, 5, 10, 25, 50, 100]);
*
* console.log(levels.higher(10)); // 25;
* console.log(levels.higher(100)); // undefined;
*/
higher(key: K): K | undefined {
this._validateKey(key);
return this.#core.higher(key);
}
/**
* Largest key that is < the given key.
* @example
* // Largest element strictly < target
* const tiers = new TreeSet<number>([100, 200, 500, 1000]);
*
* console.log(tiers.lower(500)); // 200;
* console.log(tiers.lower(100)); // undefined;
*/
lower(key: K): K | undefined {
this._validateKey(key);
return this.#core.lower(key);
}
/**
* Return all keys in a given range.
*
* @param range `[low, high]`
* @param options Inclusive/exclusive bounds (defaults to inclusive).
* @example
* // IP address blocklist with range checking
* // Simplified: use numeric IP representation
* const blocklist = new TreeSet<number>([
* 167772160, // 10.0.0.0
* 167772416, // 10.0.1.0
* 167772672, // 10.0.2.0
* 167773184 // 10.0.4.0
* ]);
*
* // Check if any blocked IP is in range 10.0.1.0 - 10.0.3.0
* const inRange = blocklist.rangeSearch([167772416, 167772928]);
* console.log(inRange); // [167772416, 167772672];
*
* // Quick membership check
* console.log(blocklist.has(167772416)); // true;
* console.log(blocklist.has(167772800)); // false;
*/
rangeSearch(range: [K, K], options: TreeSetRangeOptions = {}): K[] {
const { lowInclusive = true, highInclusive = true } = options;
const [low, high] = range;
this._validateKey(low);
this._validateKey(high);
const keys = this.#core.rangeSearch([low, high]) as (K | undefined)[];
const out: K[] = [];
const cmp = this.#core.comparator;
for (const k of keys) {
/* istanbul ignore next -- defensive: tree keys are never undefined */ if (k === undefined) continue;
if (!lowInclusive && cmp(k, low) === 0) continue;
if (!highInclusive && cmp(k, high) === 0) continue;
out.push(k);
}
return out;
}
// ─── Order-Statistic Methods ───────────────────────────
/**
* Returns the element at the k-th position in tree order (0-indexed).
* @remarks Time O(log n). Requires `enableOrderStatistic: true`.
* @example
* // Find k-th element in a TreeSet
* const set = new TreeSet<number>([30, 10, 50, 20, 40], { enableOrderStatistic: true });
* console.log(set.getByRank(0)); // 10;
* console.log(set.getByRank(2)); // 30;
* console.log(set.getRank(30)); // 2;
*/
getByRank(k: number): K | undefined {
return this.#core.getByRank(k);
}
/**
* Returns the 0-based rank of a key (number of elements that precede it in tree order).
* @remarks Time O(log n). Requires `enableOrderStatistic: true`.
* @example
* // Get the rank of a key in sorted order
* const tree = new TreeSet<number>(
* [10, 20, 30, 40, 50],
* { enableOrderStatistic: true }
* );
* console.log(tree.getRank(10)); // 0; // smallest → rank 0
* console.log(tree.getRank(30)); // 2; // 2 elements before 30 in tree order
* console.log(tree.getRank(50)); // 4; // largest → rank 4
* console.log(tree.getRank(25)); // 2;
*/
getRank(key: K): number {
return this.#core.getRank(key);
}
/**
* Returns elements by rank range (0-indexed, inclusive on both ends).
* @remarks Time O(log n + k). Requires `enableOrderStatistic: true`.
* @example
* // Pagination by position in tree order
* const tree = new TreeSet<number>(
* [10, 20, 30, 40, 50, 60, 70, 80, 90],
* { enableOrderStatistic: true }
* );
* const pageSize = 3;
*
* // Page 1
* console.log(tree.rangeByRank(0, pageSize - 1)); // [10, 20, 30];
* // Page 2
* console.log(tree.rangeByRank(pageSize, 2 * pageSize - 1)); // [40, 50, 60];
* // Page 3
* console.log(tree.rangeByRank(2 * pageSize, 3 * pageSize - 1)); // [70, 80, 90];
*/
rangeByRank(start: number, end: number): K[] {
return this.#core.rangeByRank(start, end).filter((k): k is K => k !== undefined);
}
/**
* Creates a shallow clone of this set.
* @remarks Time O(n log n), Space O(n)
* @example
* // Deep clone
* const ts = new TreeSet<number>([1, 2, 3]);
* const copy = ts.clone();
* copy.delete(1);
* console.log(ts.has(1)); // true;
*/
// ---- ES2025 Set-like operations ----
/**
* Return a new TreeSet containing all elements from both sets.
* @remarks When both sets share the same comparator, uses O(n+m) merge. Otherwise O(m log n).
* @param other - Any iterable of keys.
* @returns A new TreeSet.
* @example
* // Merge two sets
* console.log([...a.union(b)]); // [1, 2, 3, 4, 5, 6, 7];
*/
union(other: Iterable<K>): TreeSet<K> {
const result = this.clone();
for (const key of other) result.add(key);
return result;
}
/**
* Return a new TreeSet containing only elements present in both sets.
* @remarks Time O(n+m) with ordered merge when possible, otherwise O(n log m).
* @param other - Any iterable of keys (converted to Set for fast lookup if not a TreeSet).
* @returns A new TreeSet.
* @example
* // Find common elements
* console.log([...a.intersection(b)]); // [3, 4, 5];
*/
intersection(other: Iterable<K>): TreeSet<K> {
const otherSet = other instanceof TreeSet || other instanceof Set ? other : new Set(other);
const result = new TreeSet<K>([], { comparator: this.#isDefaultComparator ? undefined : this.#userComparator });
for (const key of this) {
if (otherSet.has(key)) result.add(key);
}
return result;
}
/**
* Return a new TreeSet containing elements in this set but not in the other.
* @remarks Time O(n+m) with ordered merge when possible, otherwise O(n log m).
* @param other - Any iterable of keys.
* @returns A new TreeSet.
* @example
* // Find exclusive elements
* console.log([...a.difference(b)]); // [1, 2];
*/
difference(other: Iterable<K>): TreeSet<K> {
const otherSet = other instanceof TreeSet || other instanceof Set ? other : new Set(other);
const result = new TreeSet<K>([], { comparator: this.#isDefaultComparator ? undefined : this.#userComparator });
for (const key of this) {
if (!otherSet.has(key)) result.add(key);
}
return result;
}
/**
* Return a new TreeSet containing elements in either set but not both.
* @remarks Time O(n+m).
* @param other - Any iterable of keys.
* @returns A new TreeSet.
* @example
* // Find symmetric difference
* console.log([...a.symmetricDifference(b)]); // [1, 2, 6, 7];
*/
symmetricDifference(other: Iterable<K>): TreeSet<K> {
const otherSet = other instanceof TreeSet || other instanceof Set ? other : new Set(other);
const result = new TreeSet<K>([], { comparator: this.#isDefaultComparator ? undefined : this.#userComparator });
for (const key of this) {
if (!otherSet.has(key)) result.add(key);
}
for (const key of otherSet) {
if (!this.has(key)) result.add(key);
}
return result;
}
/**
* Check whether every element in this set is also in the other.
* @remarks Time O(n).
* @param other - Any iterable of keys (converted to Set for fast lookup if not a TreeSet).
* @returns `true` if this is a subset of other.
* @example
* // Check subset
* console.log(new TreeSet([3, 4]).isSubsetOf(a)); // true;
*/
isSubsetOf(other: Iterable<K>): boolean {
const otherSet = other instanceof TreeSet || other instanceof Set ? other : new Set(other);
for (const key of this) {
if (!otherSet.has(key)) return false;
}
return true;
}
/**
* Check whether every element in the other set is also in this set.
* @remarks Time O(m).
* @param other - Any iterable of keys.
* @returns `true` if this is a superset of other.
* @example
* // Check superset
* console.log(a.isSupersetOf(new TreeSet([2, 3]))); // true;
*/
isSupersetOf(other: Iterable<K>): boolean {
for (const key of other) {
if (!this.has(key)) return false;
}
return true;
}
/**
* Check whether this set and the other share no common elements.
* @remarks Time O(min(n,m)), can short-circuit on first overlap.
* @param other - Any iterable of keys (converted to Set for fast lookup if not a TreeSet).
* @returns `true` if sets are disjoint.
* @example
* // Check disjoint
* console.log(a.isDisjointFrom(new TreeSet([8, 9]))); // true;
*/
isDisjointFrom(other: Iterable<K>): boolean {
const otherSet = other instanceof TreeSet || other instanceof Set ? other : new Set(other);
for (const key of this) {
if (otherSet.has(key)) return false;
}
return true;
}
/**
* Deep copy
* @example
* // Deep clone
* const ts = new TreeSet<number>([1, 2, 3]);
* const copy = ts.clone();
* copy.delete(1);
* console.log(ts.has(1)); // true;
*/
clone(): TreeSet<K> {
return new TreeSet<K>(this, {
comparator: this.#isDefaultComparator ? undefined : this.#userComparator,
isMapMode: this.#core.isMapMode
});
}
}