Skip to content

Commit

Permalink
feat: Added uniqueBy and asyncFlatMap (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
zgavin authored Mar 15, 2024
1 parent cbcf7a1 commit 8f0f290
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 5 deletions.
14 changes: 14 additions & 0 deletions src/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 26 additions & 5 deletions src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare global {
interface Array<T> {
unique(): Array<T>;
uniqueByKey(key: keyof T): Array<T>;
uniqueBy(f: (el: T, index: number, array: T[]) => PropertyKey): Array<T>;
compact(): Array<NonNullable<T>>;
isEmpty: boolean;
nonEmpty: boolean;
Expand Down Expand Up @@ -31,6 +32,7 @@ declare global {
asyncFilter(predicate: (v: T) => Promise<boolean>): Promise<Array<T>>;
asyncSome(predicate: (v: T) => boolean | Promise<boolean>): Promise<boolean>;
asyncMap<V>(f: (el: T, index: number, array: T[]) => Promise<V>): Promise<V[]>;
asyncFlatMap<V>(f: (el: T, index: number, array: T[]) => Promise<V | V[]>): Promise<V[]>;
asyncForEach(f: (el: T, index: number, array: T[]) => Promise<any>): Promise<void>;
sum(this: Array<number | undefined>): number;
sum(f: (el: T, index: number, array: T[]) => number | undefined): number;
Expand Down Expand Up @@ -74,6 +76,9 @@ declare global {
}

interface ReadonlyArray<T> {
unique(): Array<T>;
uniqueByKey(key: keyof T): Array<T>;
uniqueBy(f: (el: T, index: number, array: T[]) => PropertyKey): Array<T>;
compact(): Array<NonNullable<T>>;
isEmpty: boolean;
nonEmpty: boolean;
Expand All @@ -100,6 +105,7 @@ declare global {
asyncFilter(predicate: (v: T) => Promise<boolean>): Promise<Array<T>>;
asyncSome(predicate: (v: T) => boolean | Promise<boolean>): Promise<boolean>;
asyncMap<V>(f: (el: T, index: number, array: T[]) => Promise<V>): Promise<V[]>;
asyncFlatMap<V>(f: (el: T, index: number, array: T[]) => Promise<V | V[]>): Promise<V[]>;
asyncForEach(f: (el: T, index: number, array: T[]) => Promise<any>): Promise<void>;
sum(this: ReadonlyArray<number | undefined>): number;
sum(f: (el: T, index: number, array: ReadonlyArray<T>) => number | undefined): number;
Expand Down Expand Up @@ -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 <T>(key: keyof T): T[] {
Array.prototype.uniqueBy = function <T>(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<PropertyKey>();
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 <T>(key: keyof T): T[] {
return this.uniqueBy((el) => el[key]);
};

Array.prototype.compact = function () {
return this.filter(isDefined);
};
Expand Down Expand Up @@ -236,6 +250,13 @@ Array.prototype.asyncMap = async function <T, V>(
return Promise.all(this.map(f));
};

Array.prototype.asyncFlatMap = async function <T, V>(
this: Array<T>,
f: (el: T, index: number, array: T[]) => Promise<V | V[]>,
): Promise<V[]> {
return Promise.all(this.map(f)).then((result) => result.flat(1) as V[]);
};

Array.prototype.asyncForEach = async function <T>(
this: Array<T>,
f: (el: T, index: number, array: T[]) => Promise<any>,
Expand Down

0 comments on commit 8f0f290

Please sign in to comment.