"use strict"; /** * A subset of Set that offers sorting functionality * @template T item type in set * @extends {Set<T>} */ class SortableSet extends Set { /** * Create a new sortable set * @param {Iterable<T>=} initialIterable The initial iterable value * @typedef {function(T, T): number} SortFunction * @param {SortFunction=} defaultSort Default sorting function */ constructor(initialIterable, defaultSort) { super(initialIterable); /** @private @type {function(T, T): number}} */ this._sortFn = defaultSort; /** @private @type {function(T, T): number} | null} */ this._lastActiveSortFn = null; /** @private @type {Map<Function, T[]> | undefined} */ this._cache = undefined; /** @private @type {Map<Function, T[]|string|number> | undefined} */ this._cacheOrderIndependent = undefined; } /** * @param {T} value value to add to set * @returns {this} returns itself */ add(value) { this._lastActiveSortFn = null; this._invalidateCache(); this._invalidateOrderedCache(); super.add(value); return this; } /** * @param {T} value value to delete * @returns {boolean} true if value existed in set, false otherwise */ delete(value) { this._invalidateCache(); this._invalidateOrderedCache(); return super.delete(value); } /** * @returns {void} */ clear() { this._invalidateCache(); this._invalidateOrderedCache(); return super.clear(); } /** * Sort with a comparer function * @param {SortFunction} sortFn Sorting comparer function * @returns {void} */ sortWith(sortFn) { if (this.size <= 1 || sortFn === this._lastActiveSortFn) { // already sorted - nothing to do return; } const sortedArray = Array.from(this).sort(sortFn); super.clear(); for (let i = 0; i < sortedArray.length; i += 1) { super.add(sortedArray[i]); } this._lastActiveSortFn = sortFn; this._invalidateCache(); } sort() { this.sortWith(this._sortFn); } /** * Get data from cache * @param {function(SortableSet<T>): T[]} fn function to calculate value * @returns {T[]} returns result of fn(this), cached until set changes */ getFromCache(fn) { if (this._cache === undefined) { this._cache = new Map(); } else { const data = this._cache.get(fn); if (data !== undefined) { return data; } } const newData = fn(this); this._cache.set(fn, newData); return newData; } /** * @param {function(SortableSet<T>): string|number|T[]} fn function to calculate value * @returns {any} returns result of fn(this), cached until set changes */ getFromUnorderedCache(fn) { if (this._cacheOrderIndependent === undefined) { this._cacheOrderIndependent = new Map(); } else { const data = this._cacheOrderIndependent.get(fn); if (data !== undefined) { return data; } } const newData = fn(this); this._cacheOrderIndependent.set(fn, newData); return newData; } /** * @private * @returns {void} */ _invalidateCache() { if (this._cache !== undefined) { this._cache.clear(); } } /** * @private * @returns {void} */ _invalidateOrderedCache() { if (this._cacheOrderIndependent !== undefined) { this._cacheOrderIndependent.clear(); } } } module.exports = SortableSet;