class ArrayExt { public static contains(array: Array, value: T): boolean { return array.some(t => t === value); } public static orderBy(array: Array): Array; public static orderBy(array: Array, compareFunc: (value: T) => any): Array; public static orderBy(array: Array, compareFunc?: (value: T) => any): Array { if (compareFunc == null) compareFunc = (value: T): T => value; return array.toSorted((a, b) => { const valA = compareFunc(a); const valB = compareFunc(b); if (valA < valB) return -1; if (valA > valB) return 1; return 0; }); } public static orderByDesc(array: Array): Array; public static orderByDesc(array: Array, compareFunc: (value: T) => any): Array; public static orderByDesc(array: Array, compareFunc?: (value: T) => any): Array { if (compareFunc == null) compareFunc = (value: T): T => value; return array.toSorted((a, b) => { const valA = compareFunc(a); const valB = compareFunc(b); if (valB < valA) return -1; if (valB > valA) return 1; return 0; }); } // public static groupBy(array: T[], keyFunc: (value: T) => string): { [index: string]: T[] } // { // return array.reduce((acc: { [index: string]: T[] }, t) => // { // const key = keyFunc(t); // if (!acc[key]) // acc[key] = []; // acc[key].push(t); // return acc; // }, {}); // } public static groupBy(array: Array, keyFunc: (value: T) => string): Array<{ key: string; values: Array; }> { const result = new Array<{ key: string; values: Array; }>(); array.reduce>>((acc, t) => { const key = keyFunc(t); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!acc[key]) { acc[key] = []; result.push({ key, values: acc[key] }); } acc[key].push(t); return acc; }, {}); return result; } public static distinct(array: Array): Array; public static distinct(array: Array, compareFunc: (value: T) => any): Array; public static distinct(array: Array, compareFunc?: (value: T) => any): Array { // if (compareFunc == null) // compareFunc = (value: T) => value; // let internalArray: T[] = []; // for (let i = 0; i < array.length; i++) // { // let item = array[i]; // if (internalArray.some(t => compareFunc(t) === compareFunc(item))) // continue; // internalArray.push(item); // } // return internalArray; // BECAUSE WE USE SETS const setLimit = 16777216; if (array.length > setLimit) throw new Error(`Array has ${array.length} items (exceeds set limit of ${setLimit}). Calling distinct is prohibited.`); if (compareFunc == null) return [...new Set(array)]; const set = new Set(); const internalArray: Array = []; let item: T; let distinguished: any; for (let i = 0; i < array.length; i++) { item = array[i]; distinguished = compareFunc(item); if (!set.has(distinguished)) { set.add(distinguished); internalArray.push(item); } } return internalArray; } public static skip(array: Array, count: number): Array { if (count <= 0) count = 0; // let result = new Array(); // for (let i = count; i < array.length; i++) // { // result.push(array[i]); // } // return result; return array.slice(count); } public static take(array: Array, count: number): Array { if (count <= 0) count = 0; else if (count > array.length) count = array.length; // let result = new Array(); // for (let i = 0; i < count; i++) // { // result.push(array[i]); // } // return result; if (count === 0) return []; return array.slice(0, count); } public static count(array: Array): number; public static count(array: Array, predicate: (value: T) => boolean): number; public static count(array: Array, predicate?: (value: T) => boolean): number { if (predicate == null) { return array.length; } else { let count = 0; for (let i = 0; i < array.length; i++) { if (predicate(array[i])) count++; } return count; } } public static remove(array: Array, value: T): boolean { const index = array.indexOf(value); if (index < 0) return false; array.splice(index, 1); return true; } public static clear(array: Array): void { // while (array.length > 0) // { // array.pop(); // } array.length = 0; } public static equals(array: Array, compareArray: Array): boolean; public static equals(array: Array, compareArray: Array, compareFunc: (t1: T, t2: T) => boolean): boolean; public static equals(array: Array, compareArray: Array, compareFunc?: (t1: T, t2: T) => boolean): boolean { if (array === compareArray) return true; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (array === null || compareArray === null) return false; if (!(array instanceof Array) || !(compareArray instanceof Array)) return false; if (array.length !== compareArray.length) return false; if (compareFunc) { for (let i = 0; i < array.length; i++) { if (compareFunc(array[i], compareArray[i])) continue; return false; } } else { for (let i = 0; i < array.length; i++) { if (array[i] === compareArray[i]) continue; return false; } } return true; } // public static async forEachAsync(array: T[], asyncFunc: (input: T) => Promise, degreesOfParallelism?: number): Promise // { // let taskManager = new TaskManager(array, asyncFunc, degreesOfParallelism, false); // await taskManager.execute(); // } public static async forEachAsync(array: Array, asyncFunc: (input: T) => Promise, degreesOfParallelism?: number): Promise { if (array.length === 0) return; let parallelism = degreesOfParallelism ?? array.length; parallelism = Math.max(parallelism, 1); parallelism = Math.min(parallelism, array.length); if (parallelism === array.length) { await Promise.all(array.map(t => asyncFunc(t))); return; } const iterator = array[Symbol.iterator](); const worker = async (): Promise => { for (const item of iterator) await asyncFunc(item); }; const workers = new Array>(); for (let i = 0; i < parallelism; i++) workers.push(worker()); await Promise.all(workers); } // public static async mapAsync(array: T[], asyncFunc: (input: T) => Promise, degreesOfParallelism?: number): Promise // { // let taskManager = new TaskManager(array, asyncFunc, degreesOfParallelism, true); // await taskManager.execute(); // return taskManager.getResults(); // } public static async mapAsync(array: Array, asyncFunc: (input: T) => Promise, degreesOfParallelism?: number): Promise> { if (array.length === 0) return new Array(); let parallelism = degreesOfParallelism ?? array.length; parallelism = Math.max(parallelism, 1); parallelism = Math.min(parallelism, array.length); if (parallelism === array.length) return Promise.all(array.map(t => asyncFunc(t))); const results = new Array(array.length); const iterator = array.entries(); const worker = async (): Promise => { for (const [index, item] of iterator) results[index] = await asyncFunc(item); }; const workers = new Array>(); for (let i = 0; i < parallelism; i++) workers.push(worker()); await Promise.all(workers); return results; } public static async reduceAsync(array: Array, asyncFunc: (acc: U, input: T) => Promise, accumulator?: U): Promise { let index = 0; if (accumulator == null) { accumulator = array[0] ?? {}; index = 1; } for (let i = index; i < array.length; i++) accumulator = await asyncFunc(accumulator!, array[i]); return accumulator!; } } // class TaskExec // { // private readonly _array: Array; // private readonly _taskFunc: (input: T) => Promise; // private readonly _captureResults: boolean; // private readonly _results = new Array(); // private _executionPromise: Promise> | null = null; // public constructor(array: Array, taskFunc: (input: T) => Promise, captureResults: boolean) // { // this._array = array; // this._taskFunc = taskFunc; // this._captureResults = captureResults; // } // public execute(): Promise> // { // if (this._executionPromise != null) // return this._executionPromise; // this._executionPromise = this._execute().then(() => this._results); // return this._executionPromise; // } // private async _execute(): Promise // { // for (const item of this._array) // { // const result = await this._taskFunc(item); // if (this._captureResults) // this._results.push(result); // } // } // } // class BatchTaskExec // { // private readonly _array: Array; // private readonly _taskFunc: (input: T) => Promise; // private readonly _captureResults: boolean; // private readonly _taskCount: number; // public constructor(array: Array, taskFunc: (input: T) => Promise, captureResults: boolean, taskCount?: number) // { // this._array = array; // this._taskFunc = taskFunc; // this._captureResults = captureResults; // taskCount = taskCount ?? array.length; // taskCount = Math.max(taskCount, 1); // taskCount = Math.min(taskCount, array.length); // this._taskCount = taskCount; // } // // BROKEN // // public async process(): Promise> // // { // // if (this._taskCount === this._array.length) // // return await Promise.all(this._array.map(t => this._taskFunc(t))); // // const batchSize = Math.floor(this._array.length / this._taskCount); // // console.log("BATCH SIZE", batchSize); // // const promises = new Array>(); // // for (let i = 0; i < this._taskCount; i++) // // { // // const isLast = i === (this._taskCount - 1); // // const taskExec = new TaskExec(this._array.skip(i * batchSize).take(isLast ? this._array.length : batchSize), // // this._taskFunc, this._captureResults); // // promises.push(taskExec.execute()); // // } // // const results = await Promise.all(promises); // // if (!this._captureResults) // // return new Array(); // // return results.reduce((acc, items) => // // { // // acc.push(...items); // // return acc; // // }, new Array()); // // } // // Round robin // // public async process(): Promise> // // { // // if (this._taskCount === this._array.length) // // return await Promise.all(this._array.map(t => this._taskFunc(t))); // // const pools = new Array>(); // // for (let i = 0; i < this._taskCount; i++) // // pools.push([]); // // let poolIndex = 0; // // for (let i = 0; i < this._array.length; i++) // // { // // if (poolIndex >= pools.length) // // poolIndex = 0; // // const pool = pools[poolIndex]; // // pool.push(this._array[i]); // // poolIndex++; // // } // // const promises = new Array>(); // // for (let i = 0; i < this._taskCount; i++) // // { // // const taskExec = new TaskExec(pools[i], this._taskFunc, this._captureResults); // // promises.push(taskExec.execute()); // // } // // const results = await Promise.all(promises); // // if (!this._captureResults) // // return new Array(); // // const maxLength = Math.max(...results.map(t => t.length)); // // const finalResults = new Array(); // // for (let i = 0; i < maxLength; i++) // // { // // for (let j = 0; j < pools.length; j++) // // { // // const value = results[j][i]; // // if (value !== undefined) // // finalResults.push(value); // // } // // } // // return finalResults; // // } // // Remainder Round Robin // public async process(): Promise> // { // if (this._taskCount === this._array.length) // return Promise.all(this._array.map(t => this._taskFunc(t))); // const remainder = this._array.length % this._taskCount; // const batchSize = (this._array.length - remainder) / this._taskCount; // // console.log("BATCH SIZE", batchSize); // // console.log("REMAINDER", remainder); // const promises = new Array>>(); // const hasRemainder = remainder > 0; // const pools = new Array>(); // for (let i = 0; i < this._taskCount; i++) // pools.push(ArrayExt.take(ArrayExt.skip(this._array, i * batchSize), batchSize)); // if (hasRemainder) // { // const baseLength = this._array.length - remainder; // let arrayIndex, poolIndex; // for (arrayIndex = baseLength, poolIndex = 0; arrayIndex < this._array.length; arrayIndex++, poolIndex++) // { // pools[poolIndex].push(this._array[arrayIndex]); // } // } // // console.log("POOLS", pools); // for (let i = 0; i < this._taskCount; i++) // { // const taskExec = new TaskExec(pools[i], this._taskFunc, this._captureResults); // promises.push(taskExec.execute()); // } // const results = await Promise.all(promises); // if (!this._captureResults) // return new Array(); // if (hasRemainder) // { // const remaining = new Array(); // const baseLength = this._array.length - remainder; // let arrayIndex, poolIndex; // for (arrayIndex = baseLength, poolIndex = 0; arrayIndex < this._array.length; arrayIndex++, poolIndex++) // { // pools[poolIndex].push(this._array[arrayIndex]); // const poolResults = results[poolIndex]; // remaining.push(poolResults[poolResults.length - 1]); // poolResults.splice(results[poolIndex].length - 1, 1); // } // const actualResults = results.reduce((acc, items) => // { // acc.push(...items); // return acc; // }, new Array()); // actualResults.push(...remaining); // return actualResults; // } // else // { // return results.reduce((acc, items) => // { // acc.push(...items); // return acc; // }, new Array()); // } // } // } // class TaskManager // { // private readonly _array: Array; // private readonly _taskFunc: (input: T) => Promise; // private readonly _taskCount: number; // private readonly _captureResults: boolean; // private readonly _tasks: Array>; // private readonly _results: Array = []; // public constructor(array: Array, taskFunc: (input: T) => Promise, taskCount: number, captureResults: boolean) // { // this._array = array; // this._taskFunc = taskFunc; // this._taskCount = !taskCount || taskCount <= 0 ? this._array.length : taskCount; // this._captureResults = captureResults; // this._tasks = []; // for (let i = 0; i < this._taskCount; i++) // this._tasks.push(new Task(this, i, this._taskFunc, captureResults)); // } // public async execute(): Promise // { // for (let i = 0; i < this._array.length; i++) // { // if (this._captureResults) // this._results.push(null); // await this._executeTaskForItem(this._array[i], i); // } // await this._finish(); // } // public addResult(itemIndex: number, result: any): void // { // this._results[itemIndex] = result; // } // public getResults(): Array // { // return this._results; // } // private async _executeTaskForItem(item: T, itemIndex: number): Promise // { // let availableTask = this._tasks.find(t => t.isFree); // if (!availableTask) // { // const task = await Promise.race(this._tasks.map(t => t.promise!)); // task.free(); // availableTask = task; // } // availableTask.execute(item, itemIndex); // } // private _finish(): Promise // { // // eslint-disable-next-line @typescript-eslint/await-thenable // return Promise.all(this._tasks.filter(t => !t.isFree).map(t => t.promise)); // } // } // class Task // { // private readonly _manager: TaskManager; // // @ts-expect-error: not used atm // private readonly _id: number; // private readonly _taskFunc: (input: T) => Promise; // private readonly _captureResult: boolean; // private _promise: Promise> | null; // public get promise(): Promise> | null { return this._promise; } // public get isFree(): boolean { return this._promise === null; } // public constructor(manager: TaskManager, id: number, taskFunc: (input: T) => Promise, captureResult: boolean) // { // this._manager = manager; // this._id = id; // this._taskFunc = taskFunc; // this._captureResult = captureResult; // this._promise = null; // } // public execute(item: T, itemIndex: number): void // { // this._promise = new Promise((resolve, reject) => // { // this._taskFunc(item) // .then((result) => // { // if (this._captureResult) // this._manager.addResult(itemIndex, result); // resolve(this); // }) // .catch((err) => reject(err)); // }); // } // public free(): void // { // this._promise = null; // } // } function defineArrayExtProperties(): void { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["isEmpty"] === undefined) Object.defineProperty(Array.prototype, "isEmpty", { configurable: false, enumerable: false, get: function () { return this.length === 0; } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["isNotEmpty"] === undefined) Object.defineProperty(Array.prototype, "isNotEmpty", { configurable: false, enumerable: false, get: function () { return this.length > 0; } }); // // @ts-ignore // if (Array.prototype["first"] === undefined) // Object.defineProperty(Array.prototype, "first", { // configurable: false, // enumerable: false, // get: function () // { // // if (this.length === 0) // // throw new Error("Invalid Operation: Array is empty"); // if (this.length === 0) // return undefined; // return this[0]; // } // }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["takeFirst"] === undefined) Object.defineProperty(Array.prototype, "takeFirst", { configurable: false, enumerable: false, writable: false, value: function (): any { if (this.length === 0) throw new Error("Invalid Operation: Array is empty"); // if (this.length === 0) // return undefined; return this[0]; } }); // // @ts-ignore // if (Array.prototype["last"] === undefined) // Object.defineProperty(Array.prototype, "last", { // configurable: false, // enumerable: false, // get: function () // { // if (this.length === 0) // throw new Error("Invalid Operation: Array is empty"); // // if (this.length === 0) // // return undefined; // return this[this.length - 1]; // } // }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["takeLast"] === undefined) Object.defineProperty(Array.prototype, "takeLast", { configurable: false, enumerable: false, writable: false, value: function (): any { if (this.length === 0) throw new Error("Invalid Operation: Array is empty"); // if (this.length === 0) // return undefined; return this[this.length - 1]; } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["contains"] === undefined) Object.defineProperty(Array.prototype, "contains", { configurable: false, enumerable: false, writable: false, value: function (value: any): boolean { return ArrayExt.contains(this, value); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["where"] === undefined) Object.defineProperty(Array.prototype, "where", { configurable: false, enumerable: false, writable: false, value: function (filterFunc: (value: any) => boolean): Array { return (>this).filter(filterFunc); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["orderBy"] === undefined) Object.defineProperty(Array.prototype, "orderBy", { configurable: false, enumerable: false, writable: false, value: function (compareFunc?: (value: any) => any): Array { return ArrayExt.orderBy(this, compareFunc!); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["orderByDesc"] === undefined) Object.defineProperty(Array.prototype, "orderByDesc", { configurable: false, enumerable: false, writable: false, value: function (compareFunc?: (value: any) => any): Array { return ArrayExt.orderByDesc(this, compareFunc!); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["groupBy"] === undefined) Object.defineProperty(Array.prototype, "groupBy", { configurable: false, enumerable: false, writable: false, value: function (keyFunc: (value: any) => string): Array<{ key: string; values: Array; }> { return ArrayExt.groupBy(this, keyFunc); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["distinct"] === undefined) Object.defineProperty(Array.prototype, "distinct", { configurable: false, enumerable: false, writable: false, value: function (compareFunc?: (value: any) => any): Array { return ArrayExt.distinct(this, compareFunc!); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["skip"] === undefined) Object.defineProperty(Array.prototype, "skip", { configurable: false, enumerable: false, writable: false, value: function (count: number): Array { return ArrayExt.skip(this, count); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["take"] === undefined) Object.defineProperty(Array.prototype, "take", { configurable: false, enumerable: false, writable: false, value: function (count: number): Array { return ArrayExt.take(this, count); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["count"] === undefined) Object.defineProperty(Array.prototype, "count", { configurable: false, enumerable: false, writable: false, value: function (predicate?: (value: any) => boolean): number { return ArrayExt.count(this, predicate!); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["remove"] === undefined) Object.defineProperty(Array.prototype, "remove", { configurable: false, enumerable: false, writable: true, // for spread.js compatibility value: function (value: any): boolean { return ArrayExt.remove(this, value); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["clear"] === undefined) Object.defineProperty(Array.prototype, "clear", { configurable: false, enumerable: false, writable: true, // for spread.js compatibility value: function (): void { ArrayExt.clear(this); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["equals"] === undefined) Object.defineProperty(Array.prototype, "equals", { configurable: false, enumerable: false, writable: false, value: function (compareArray: Array, compareFunc?: (t1: any, t2: any) => boolean): boolean { return ArrayExt.equals(this, compareArray, compareFunc!); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["forEachAsync"] === undefined) Object.defineProperty(Array.prototype, "forEachAsync", { configurable: false, enumerable: false, writable: false, value: function (asyncFunc: (input: any) => Promise, degreesOfParallelism?: number): Promise { return ArrayExt.forEachAsync(this, asyncFunc, degreesOfParallelism); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["mapAsync"] === undefined) Object.defineProperty(Array.prototype, "mapAsync", { configurable: false, enumerable: false, writable: false, value: function (asyncFunc: (input: any) => Promise, degreesOfParallelism?: number): Promise> { return ArrayExt.mapAsync(this, asyncFunc, degreesOfParallelism); } }); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (Array.prototype["reduceAsync"] === undefined) Object.defineProperty(Array.prototype, "reduceAsync", { configurable: false, enumerable: false, writable: false, value: function (asyncFunc: (acc: any, input: any) => Promise, accumulator?: any): Promise { return ArrayExt.reduceAsync(this, asyncFunc, accumulator); } }); } defineArrayExtProperties();