diff --git a/src/array.test.ts b/src/array.test.ts index ef6137d..be9fd46 100644 --- a/src/array.test.ts +++ b/src/array.test.ts @@ -109,6 +109,20 @@ describe("array", () => { }); }); + describe("uniqueBy", () => { + it("removes duplicates", () => { + const arr = [{ foo: 1 }, { foo: 2 }, { foo: 1 }]; + const result = arr.uniqueBy((obj) => obj.foo); + expect(result).toEqual([{ foo: 1 }, { foo: 2 }]); + }); + + it("uses the first object if there are multiple matching elements in the array", () => { + const arr = [{ foo: 1, bar: 2 }, { foo: 2 }, { foo: 1, baz: 3 }]; + const result = arr.uniqueBy((obj) => obj.foo); + expect(result).toEqual([{ foo: 1, bar: 2 }, { foo: 2 }]); + }); + }); + describe("everyHasSame", () => { it("returns true if all elements match", () => { // given everything has the same 'name' property diff --git a/src/array.ts b/src/array.ts index d67961f..1ce52aa 100644 --- a/src/array.ts +++ b/src/array.ts @@ -4,6 +4,7 @@ declare global { interface Array { unique(): Array; uniqueByKey(key: keyof T): Array; + uniqueBy(f: (el: T, index: number, array: T[]) => PropertyKey): Array; compact(): Array>; isEmpty: boolean; nonEmpty: boolean; @@ -31,6 +32,7 @@ declare global { asyncFilter(predicate: (v: T) => Promise): Promise>; asyncSome(predicate: (v: T) => boolean | Promise): Promise; asyncMap(f: (el: T, index: number, array: T[]) => Promise): Promise; + asyncFlatMap(f: (el: T, index: number, array: T[]) => Promise): Promise; asyncForEach(f: (el: T, index: number, array: T[]) => Promise): Promise; sum(this: Array): number; sum(f: (el: T, index: number, array: T[]) => number | undefined): number; @@ -74,6 +76,9 @@ declare global { } interface ReadonlyArray { + unique(): Array; + uniqueByKey(key: keyof T): Array; + uniqueBy(f: (el: T, index: number, array: T[]) => PropertyKey): Array; compact(): Array>; isEmpty: boolean; nonEmpty: boolean; @@ -100,6 +105,7 @@ declare global { asyncFilter(predicate: (v: T) => Promise): Promise>; asyncSome(predicate: (v: T) => boolean | Promise): Promise; asyncMap(f: (el: T, index: number, array: T[]) => Promise): Promise; + asyncFlatMap(f: (el: T, index: number, array: T[]) => Promise): Promise; asyncForEach(f: (el: T, index: number, array: T[]) => Promise): Promise; sum(this: ReadonlyArray): number; sum(f: (el: T, index: number, array: ReadonlyArray) => number | undefined): number; @@ -158,15 +164,23 @@ Array.prototype.unique = function () { }; /** Would be cool to allow an array of keys to make the criteria of "unique" more flexible */ -Array.prototype.uniqueByKey = function (key: keyof T): T[] { +Array.prototype.uniqueBy = function (f: (el: T, index: number, array: T[]) => PropertyKey): T[] { const result: T[] = []; - const group = this.groupBy((item) => item[key]); - Object.keys(group).forEach((gKey) => { - result.push(group[gKey].first); - }); + const set = new Set(); + for (let i = 0; i < this.length; i++) { + const key = f(this[i], i, this); + if (!set.has(key)) { + result.push(this[i]); + set.add(key); + } + } return result; }; +Array.prototype.uniqueByKey = function (key: keyof T): T[] { + return this.uniqueBy((el) => el[key]); +}; + Array.prototype.compact = function () { return this.filter(isDefined); }; @@ -236,6 +250,13 @@ Array.prototype.asyncMap = async function ( return Promise.all(this.map(f)); }; +Array.prototype.asyncFlatMap = async function ( + this: Array, + f: (el: T, index: number, array: T[]) => Promise, +): Promise { + return Promise.all(this.map(f)).then((result) => result.flat(1) as V[]); +}; + Array.prototype.asyncForEach = async function ( this: Array, f: (el: T, index: number, array: T[]) => Promise,