diff --git a/index.d.ts b/index.d.ts index 8d47d80..693142a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -288,6 +288,11 @@ declare module '@augu/collections' { */ includes(key: T): boolean; + /** + * Clones a new [[Queue]] instance with the items available + */ + clone(): this; + [Symbol.iterator](): IteratorResult; } } diff --git a/src/Collection.ts b/src/Collection.ts index 18c11a6..6befedb 100644 --- a/src/Collection.ts +++ b/src/Collection.ts @@ -1,386 +1,386 @@ -/** - * Copyright (c) 2019-2021 August - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import * as utils from './utils'; - -type ObjectLike = T extends object ? { [P in keyof T]: T[P]; } : T; -type Predicate - = (this: ThisArg, value: Value, index: Index, key: Key) => ReturnAs; - -type ReducePredicate = (this: ThisArg, acc: Acc, current: Current) => ReturnAs; -type UndetailedPredicate = (value: Value, index: Index, key: Key) => ReturnAs; -type MinimalPredicate = (this: ThisArg, value: Value) => ReturnAs; -type UndetailedMinimalPredicate = (value: Value) => ReturnAs; - -/** - * Represents a class to hold key-value pairs using [[Collection]]. This is a extension - * of [Map] to add Array-like functions and a update builder. - * - * @template K The key structure for this [[Collection]] - * @template V The value structure for this [[Collection]] - */ -export class Collection extends Map { - public ['constructor']: typeof Collection; - - /** Returns if this [[`Collection`]] is empty or not */ - get empty() { - return this.size === 0; - } - - /** - * Use a predicate function to filter out anything and return a new Array - * @param predicate The predicate function to filter out - * @param thisArg An additional `this` context if needed - * @returns A new Array of the values that returned `true` in the predicate function - */ - filter>(predicate: Predicate, thisArg?: ThisArg) { - let func: UndetailedPredicate; - - if (thisArg !== undefined) - func = predicate.bind(thisArg); - else - func = predicate.bind( this); - - const results: V[] = []; - let i = -1; - - for (const [key, value] of this.entries()) { - if (func(value, i++, key)) results.push(value); - } - - return results; - } - - /** - * Use a predicate function to map anything into a new array - * @param predicate The predicate function to map out and return a new array - * @param thisArg An additional `this` context if needed - * @returns A new Array of the values from that function - */ - map>( - predicate: Predicate, - thisArg?: ThisArg - ) { - let func: UndetailedPredicate; - - if (thisArg !== undefined) - func = predicate.bind(thisArg); - else - func = predicate.bind( this); - - const results: S[] = []; - let i = -1; - - for (const [key, value] of this.entries()) { - results.push(func(value, ++i, key)); - } - - return results; - } - - /** - * Returns a random value from the collection - * @returns A random value or `null` if the collection is empty - */ - random() { - if (this.empty) return null; - const iterable = Array.from(this.values()); - - return iterable[Math.floor(Math.random() * iterable.length)]; - } - - /** - * Reduce the collection and return a new initial value - * @param predicate The predicate function - * @param initialValue The initial value - */ - reduce( - predicate: ReducePredicate, V, S, S>, - initialValue?: S - ) { - const iterable = this.values(); - let value!: V; - let res: S = initialValue === undefined ? iterable.next().value : initialValue; - - const func = predicate.bind(this); - while ((value = iterable.next().value) !== undefined) res = func(res, value); - } - - /** - * Returns the first element in the collection - */ - first(): V | undefined; - - /** - * Returns an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - first(amount: number): V[]; - - /** - * Returns the first element in the collection or an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - first(amount?: number): V | V[] | undefined { - if (typeof amount === 'undefined') { - const iterable = this.values(); - return iterable.next().value; - } - - if (amount < 0) return this.last(amount! * -1); - amount = Math.min(amount, this.size); - - const iterable = this.values(); - return Array.from({ length: amount }, (): V => iterable.next().value); - } - - /** - * Returns the last element in the collection - */ - last(): V | undefined; - - /** - * Returns an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - last(amount: number): V[]; - - /** - * Returns the last element in the collection or an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - last(amount?: number): V | V[] | undefined { - const iter = [...this.values()]; - if (typeof amount === 'undefined') return iter[iter.length - 1]; - if (amount < 0) return this.first(amount! * -1); - if (!amount) return []; - - return iter.slice(-amount); - } - - /** - * Returns the last element in the collection - */ - lastKey(): K | undefined; - - /** - * Returns an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - lastKey(amount: number): K[]; - - /** - * Returns the last element in the collection or an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - lastKey(amount?: number): K | K[] | undefined { - const iter = [...this.keys()]; - if (typeof amount === 'undefined') return iter[iter.length - 1]; - if (amount < 0) return this.firstKey(amount! * -1); - if (!amount) return []; - - return iter.slice(-amount); - } - - /** - * Returns the first key in the collection - */ - firstKey(): K | undefined; - - /** - * Returns an Array of the keys from the correspondant `amount` - * @param amount The amount to fetch from - */ - firstKey(amount: number): K[]; - - /** - * Returns the first key in the collection or an Array of the values from the correspondant `amount` - * @param amount The amount to fetch from - */ - firstKey(amount?: number): K | K[] | undefined { - if (typeof amount === 'undefined') { - return (this.keys()).next().value; - } - - if (amount < 0) return this.lastKey(amount! * -1); - amount = Math.min(amount, this.size); - - const iterable = this.keys(); - return Array.from({ length: amount }, (): K => iterable.next().value); - } - - /** - * Returns all of the values as an Array - */ - toArray() { - return [...this.values()]; - } - - /** - * Returns all of the keys as an Array - */ - toKeyArray() { - return [...this.keys()]; - } - - /** - * Gets the first item in the collection and removes it (if provided) - * @param remove If we should remove it or not - */ - shift(remove: boolean = false) { - const item = this.first(); - const key = this.firstKey(); - if (item === undefined || key === undefined) return null; - - if (remove) this.delete(key); - return item; - } - - /** - * Gets the last item in the collection and removes it(if provided) - * @param remove If we should remove it or not - */ - unshift(remove: boolean = false) { - const item = this.last(); - const key = this.lastKey(); - - if (item === undefined || key === undefined) return null; - - if (remove) this.delete(key); - return item; - } - - /** - * Find a value in the collection from it's predicate function - * @param predicate The predicate function - * @param thisArg An additional `this` context if needed - * @returns The value found or `null` if not found - */ - find>( - predicate: MinimalPredicate, - thisArg?: ThisArg - ) { - let func: UndetailedMinimalPredicate; - - if (thisArg !== undefined) - func = predicate.bind(thisArg); - else - func = predicate.bind( this); - - let result: V | null = null; - for (const value of this.values()) { - if (func(value)) { - result = value; - break; - } - } - - return result; - } - - /** - * Computes a value if it's absent in this Collection - * @param key The key to find - * @param insert Item to add when it doesn't exist - */ - emplace(key: K, insert: V | (() => V)) { - if (!this.has(key)) { - const item = typeof insert === 'function' ? (insert as any)() : insert; - this.set(key, item); - return item as unknown as V; - } else { - return this.get(key)!; - } - } - - /** - * Similar to [Array.sort], which basically sorts the values of this Collection - * to return a value - * - * @param compareFn The compare function - * @returns The value - */ - sort(compareFn: (a: V, b: V) => number) { - const values = this.toArray(); - values.sort(compareFn); - - return values; - } - - /** - * Similar to [Array.sort], which basically sorts the values of this Collection - * to return a value - * - * @param compareFn The compare function - * @returns The value - */ - sortKeys(compareFn: (a: K, b: K) => number) { - const keys = this.toKeyArray(); - keys.sort(compareFn); - - return keys; - } - - /** - * Similar to [Array.some], this function tests whether atleast 1 item - * in the predicate function passes the test in the values cache. - * - * @param func The function to use to filter out - * @returns A boolean value if 1 item of the cache is truthy - */ - some(func: (item: V) => boolean) { - const values = this.toArray(); - - for (let i = 0; i < values.length; i++) { - if (func.call(this, values[i])) return true; - } - - return false; - } - - /** - * Similar to [Array.some], this functions tests whether atleast 1 key - * in the predicate function passes the test in the key cache. - * - * @param func The function to use to filter out - * @returns A boolean value if 1 item of the cache is truthy - */ - someKeys(func: (item: K) => boolean) { - const keys = this.toKeyArray(); - - for (let i = 0; i < keys.length; i++) { - if (func.call(this, keys[i])) return true; - } - - return false; - } - - /** - * Bulk add items into this [[`Collection`]] using an object - * @param obj The object to bulk-add to this [[`Collection`]] - */ - bulkAdd(obj: { [X in K]: V }) { - for (const [key, value] of Object.entries(obj)) { - this.emplace( key, value); - } - } -} +/** + * Copyright (c) 2019-2021 August + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as utils from './utils'; + +type ObjectLike = T extends object ? { [P in keyof T]: T[P]; } : T; +type Predicate + = (this: ThisArg, value: Value, index: Index, key: Key) => ReturnAs; + +type ReducePredicate = (this: ThisArg, acc: Acc, current: Current) => ReturnAs; +type UndetailedPredicate = (value: Value, index: Index, key: Key) => ReturnAs; +type MinimalPredicate = (this: ThisArg, value: Value) => ReturnAs; +type UndetailedMinimalPredicate = (value: Value) => ReturnAs; + +/** + * Represents a class to hold key-value pairs using [[Collection]]. This is a extension + * of [Map] to add Array-like functions and a update builder. + * + * @template K The key structure for this [[Collection]] + * @template V The value structure for this [[Collection]] + */ +export class Collection extends Map { + public ['constructor']: typeof Collection; + + /** Returns if this [[`Collection`]] is empty or not */ + get empty() { + return this.size === 0; + } + + /** + * Use a predicate function to filter out anything and return a new Array + * @param predicate The predicate function to filter out + * @param thisArg An additional `this` context if needed + * @returns A new Array of the values that returned `true` in the predicate function + */ + filter>(predicate: Predicate, thisArg?: ThisArg) { + let func: UndetailedPredicate; + + if (thisArg !== undefined) + func = predicate.bind(thisArg); + else + func = predicate.bind( this); + + const results: V[] = []; + let i = -1; + + for (const [key, value] of this.entries()) { + if (func(value, i++, key)) results.push(value); + } + + return results; + } + + /** + * Use a predicate function to map anything into a new array + * @param predicate The predicate function to map out and return a new array + * @param thisArg An additional `this` context if needed + * @returns A new Array of the values from that function + */ + map>( + predicate: Predicate, + thisArg?: ThisArg + ) { + let func: UndetailedPredicate; + + if (thisArg !== undefined) + func = predicate.bind(thisArg); + else + func = predicate.bind( this); + + const results: S[] = []; + let i = -1; + + for (const [key, value] of this.entries()) { + results.push(func(value, ++i, key)); + } + + return results; + } + + /** + * Returns a random value from the collection + * @returns A random value or `null` if the collection is empty + */ + random() { + if (this.empty) return null; + const iterable = Array.from(this.values()); + + return iterable[Math.floor(Math.random() * iterable.length)]; + } + + /** + * Reduce the collection and return a new initial value + * @param predicate The predicate function + * @param initialValue The initial value + */ + reduce( + predicate: ReducePredicate, V, S, S>, + initialValue?: S + ) { + const iterable = this.values(); + let value!: V; + let res: S = initialValue === undefined ? iterable.next().value : initialValue; + + const func = predicate.bind(this); + while ((value = iterable.next().value) !== undefined) res = func(res, value); + } + + /** + * Returns the first element in the collection + */ + first(): V | undefined; + + /** + * Returns an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + first(amount: number): V[]; + + /** + * Returns the first element in the collection or an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + first(amount?: number): V | V[] | undefined { + if (typeof amount === 'undefined') { + const iterable = this.values(); + return iterable.next().value; + } + + if (amount < 0) return this.last(amount! * -1); + amount = Math.min(amount, this.size); + + const iterable = this.values(); + return Array.from({ length: amount }, (): V => iterable.next().value); + } + + /** + * Returns the last element in the collection + */ + last(): V | undefined; + + /** + * Returns an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + last(amount: number): V[]; + + /** + * Returns the last element in the collection or an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + last(amount?: number): V | V[] | undefined { + const iter = [...this.values()]; + if (typeof amount === 'undefined') return iter[iter.length - 1]; + if (amount < 0) return this.first(amount! * -1); + if (!amount) return []; + + return iter.slice(-amount); + } + + /** + * Returns the last element in the collection + */ + lastKey(): K | undefined; + + /** + * Returns an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + lastKey(amount: number): K[]; + + /** + * Returns the last element in the collection or an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + lastKey(amount?: number): K | K[] | undefined { + const iter = [...this.keys()]; + if (typeof amount === 'undefined') return iter[iter.length - 1]; + if (amount < 0) return this.firstKey(amount! * -1); + if (!amount) return []; + + return iter.slice(-amount); + } + + /** + * Returns the first key in the collection + */ + firstKey(): K | undefined; + + /** + * Returns an Array of the keys from the correspondant `amount` + * @param amount The amount to fetch from + */ + firstKey(amount: number): K[]; + + /** + * Returns the first key in the collection or an Array of the values from the correspondant `amount` + * @param amount The amount to fetch from + */ + firstKey(amount?: number): K | K[] | undefined { + if (typeof amount === 'undefined') { + return (this.keys()).next().value; + } + + if (amount < 0) return this.lastKey(amount! * -1); + amount = Math.min(amount, this.size); + + const iterable = this.keys(); + return Array.from({ length: amount }, (): K => iterable.next().value); + } + + /** + * Returns all of the values as an Array + */ + toArray() { + return [...this.values()]; + } + + /** + * Returns all of the keys as an Array + */ + toKeyArray() { + return [...this.keys()]; + } + + /** + * Gets the first item in the collection and removes it (if provided) + * @param remove If we should remove it or not + */ + shift(remove: boolean = false) { + const item = this.first(); + const key = this.firstKey(); + if (item === undefined || key === undefined) return null; + + if (remove) this.delete(key); + return item; + } + + /** + * Gets the last item in the collection and removes it(if provided) + * @param remove If we should remove it or not + */ + unshift(remove: boolean = false) { + const item = this.last(); + const key = this.lastKey(); + + if (item === undefined || key === undefined) return null; + + if (remove) this.delete(key); + return item; + } + + /** + * Find a value in the collection from it's predicate function + * @param predicate The predicate function + * @param thisArg An additional `this` context if needed + * @returns The value found or `null` if not found + */ + find>( + predicate: MinimalPredicate, + thisArg?: ThisArg + ) { + let func: UndetailedMinimalPredicate; + + if (thisArg !== undefined) + func = predicate.bind(thisArg); + else + func = predicate.bind( this); + + let result: V | null = null; + for (const value of this.values()) { + if (func(value)) { + result = value; + break; + } + } + + return result; + } + + /** + * Computes a value if it's absent in this Collection + * @param key The key to find + * @param insert Item to add when it doesn't exist + */ + emplace(key: K, insert: V | (() => V)) { + if (!this.has(key)) { + const item = typeof insert === 'function' ? (insert as any)() : insert; + this.set(key, item); + return item as unknown as V; + } else { + return this.get(key)!; + } + } + + /** + * Similar to [Array.sort], which basically sorts the values of this Collection + * to return a value + * + * @param compareFn The compare function + * @returns The value + */ + sort(compareFn: (a: V, b: V) => number) { + const values = this.toArray(); + values.sort(compareFn); + + return values; + } + + /** + * Similar to [Array.sort], which basically sorts the values of this Collection + * to return a value + * + * @param compareFn The compare function + * @returns The value + */ + sortKeys(compareFn: (a: K, b: K) => number) { + const keys = this.toKeyArray(); + keys.sort(compareFn); + + return keys; + } + + /** + * Similar to [Array.some], this function tests whether atleast 1 item + * in the predicate function passes the test in the values cache. + * + * @param func The function to use to filter out + * @returns A boolean value if 1 item of the cache is truthy + */ + some(func: (item: V) => boolean) { + const values = this.toArray(); + + for (let i = 0; i < values.length; i++) { + if (func.call(this, values[i])) return true; + } + + return false; + } + + /** + * Similar to [Array.some], this functions tests whether atleast 1 key + * in the predicate function passes the test in the key cache. + * + * @param func The function to use to filter out + * @returns A boolean value if 1 item of the cache is truthy + */ + someKeys(func: (item: K) => boolean) { + const keys = this.toKeyArray(); + + for (let i = 0; i < keys.length; i++) { + if (func.call(this, keys[i])) return true; + } + + return false; + } + + /** + * Bulk add items into this [[`Collection`]] using an object + * @param obj The object to bulk-add to this [[`Collection`]] + */ + bulkAdd(obj: {}) { + for (const [key, value] of Object.entries(obj)) { + this.emplace( key, value); + } + } +} diff --git a/src/Queue.ts b/src/Queue.ts index b4db25e..7ee9d26 100644 --- a/src/Queue.ts +++ b/src/Queue.ts @@ -27,6 +27,7 @@ import * as utils from './utils'; * @template T The structure of this [[Queue]] instance */ export class Queue { + public ['constructor']: typeof Queue; private items: T[]; /** @@ -182,6 +183,13 @@ export class Queue { return this.items.length; } + /** + * Clones a new [[Queue]] instance with the items available + */ + clone() { + return new this.constructor(this.items); + } + [Symbol.iterator]() { let index = -1; const items = this.toArray();