From 8f6f25f1eb8bda9d0aff1d47340691057043db44 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 22:35:04 +0000 Subject: [PATCH 1/8] feat(equal): add equal function --- index.test.ts | 33 +++++++++++++++++++++++++++++++++ index.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/index.test.ts b/index.test.ts index 84f3a297..ce8d5745 100644 --- a/index.test.ts +++ b/index.test.ts @@ -9,6 +9,7 @@ import { contains, dropWhile, empty, + equal, exclude, excludeFirst, excludeNull, @@ -120,6 +121,38 @@ test("dropWhile", t => { ); }); +test("equal", t => { + t.true(equal([1, 2, 3], [1, 2, 3])); + t.false(equal([1, 2, 3], [1, 2, 3, 4])); + t.false(equal([1, 2, 3, 4], [1, 2, 3])); + t.false(equal([1, 3, 3], [1, 2, 3])); + t.true( + equal( + [ + [1, 2], + [3, 4] + ], + [ + [1, 2], + [3, 4] + ], + equal + ) + ); + t.false( + equal( + [ + [1, 2], + [3, 4] + ], + [ + [1, 2], + [3, 4] + ] + ) + ); +}); + test("map", t => { t.deepEqual( map([1, 2, 3], e => e + 1), diff --git a/index.ts b/index.ts index a3a4d9c4..e514f2a7 100644 --- a/index.ts +++ b/index.ts @@ -182,6 +182,35 @@ export function dropWhileFn( return array => dropWhile(array, predicate); } +export function equal( + a: ArrayLike, + b: ArrayLike, + elementsEqual: (a: T, b: T) => boolean = defaultEqual +): boolean { + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; ++i) { + if (!elementsEqual(a[i], b[i])) { + return false; + } + } + + return true; +} + +export function equalFn( + b: ArrayLike, + elementsEqual: (a: T, b: T) => boolean = defaultEqual +): (a: ArrayLike) => boolean { + return a => equal(a, b, elementsEqual); +} + +function defaultEqual(a: unknown, b: unknown): boolean { + return a === b; +} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore duplicate identifier: This is the exported declaration, the implementation is below. export function map(array: ArrayLike, f: (element: T, index: number) => U): U[]; From 94b05627f14c651ccabbdf91d098e5dea96aa7e6 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 22:37:30 +0000 Subject: [PATCH 2/8] feat(notequal): add notEqual function --- index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/index.ts b/index.ts index e514f2a7..7b2d97a8 100644 --- a/index.ts +++ b/index.ts @@ -207,6 +207,21 @@ export function equalFn( return a => equal(a, b, elementsEqual); } +export function notEqual( + a: ArrayLike, + b: ArrayLike, + elementsEqual: (a: T, b: T) => boolean = defaultEqual +): boolean { + return !equal(a, b, elementsEqual); +} + +export function notEqualFn( + b: ArrayLike, + elementsEqual: (a: T, b: T) => boolean = defaultEqual +): (a: ArrayLike) => boolean { + return a => notEqual(a, b, elementsEqual); +} + function defaultEqual(a: unknown, b: unknown): boolean { return a === b; } From 9b2d82a3bf906e30a81beef91f5d2825af3ab381 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 22:45:26 +0000 Subject: [PATCH 3/8] perf(zip): hopefully make zip a bit faster for large arrays --- index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.ts b/index.ts index 7b2d97a8..9f5c1a09 100644 --- a/index.ts +++ b/index.ts @@ -834,9 +834,10 @@ export function partitionWhileFn( * If one of the supplied arrays is shorter than the other, then the excess * elements of the longer array will be discarded. */ export function zip(a: readonly T[], b: readonly U[]): Array<[T, U]> { - const result: Array<[T, U]> = []; - for (let i = 0; i < a.length && i < b.length; ++i) { - result.push([a[i], b[i]]); + const length = Math.min(a.length, b.length); + const result = new Array<[T, U]>(length); + for (let i = 0; i < length; ++i) { + result[i] = [a[i], b[i]]; } return result; } From 303537f03247db1df471765089db53993bfd1694 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 22:58:31 +0000 Subject: [PATCH 4/8] feat(only): add only function --- index.test.ts | 7 +++++++ index.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/index.test.ts b/index.test.ts index ce8d5745..2c9f5577 100644 --- a/index.test.ts +++ b/index.test.ts @@ -35,6 +35,7 @@ import { mapKeyFirstBy, maximum, minimum, + only, or, partition, partitionWhile, @@ -86,6 +87,12 @@ test("last", t => { t.is(last([1, 2, 3]), 3); }); +test("only", t => { + t.is(only([]), null); + t.is(only([4]), 4); + t.is(only([3, 4, 5]), null); +}); + test("empty", t => { t.true(empty([])); t.false(empty([1, 2, 3])); diff --git a/index.ts b/index.ts index 9f5c1a09..5d28aceb 100644 --- a/index.ts +++ b/index.ts @@ -98,6 +98,12 @@ export function last(array: ArrayLike): T | null { return array.length === 0 ? null : array[array.length - 1]; } +/** If the array contains exactly one element, returns that element. + * Otherwise, returns null. */ +export function only(array: ArrayLike): T | null { + return array.length === 1 ? array[0] : null; +} + export function empty(array: ArrayLike): boolean { return array.length === 0; } From 6f4868d4d0b1a65b7ba7752c16ced978b65d3da1 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 23:23:48 +0000 Subject: [PATCH 5/8] refactor: reorder tests --- index.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.test.ts b/index.test.ts index 2c9f5577..271ed7de 100644 --- a/index.test.ts +++ b/index.test.ts @@ -490,6 +490,13 @@ test("mapKeyBy", t => { t.deepEqual(Array.from(map.keys()), ["odd", "even"]); }); +test("mapKeyFirstBy", t => { + const map = mapKeyFirstBy([1, 3, 4, 2, 5, 6], e => [e % 2 === 0 ? "even" : "odd", String(e)]); + t.is(map.get("even"), "4"); + t.is(map.get("odd"), "1"); + t.deepEqual(Array.from(map.keys()), ["odd", "even"]); +}); + test("groupByIdentity", t => { t.deepEqual( groupByIdentity(["abc", "adef", "bghi"], a => a.substr(0, 1)), @@ -504,10 +511,3 @@ test("forEach", t => { t.is(b, a); t.is(s, "abc"); }); - -test("mapKeyFirstBy", t => { - const map = mapKeyFirstBy([1, 3, 4, 2, 5, 6], e => [e % 2 === 0 ? "even" : "odd", String(e)]); - t.is(map.get("even"), "4"); - t.is(map.get("odd"), "1"); - t.deepEqual(Array.from(map.keys()), ["odd", "even"]); -}); From 089fded078e3564ae81e67ecbf523a3ea3a06cc5 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 23:27:09 +0000 Subject: [PATCH 6/8] feat(sort): add sort function --- index.test.ts | 10 ++++++++++ index.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/index.test.ts b/index.test.ts index 271ed7de..b9a5bafa 100644 --- a/index.test.ts +++ b/index.test.ts @@ -48,6 +48,7 @@ import { scanRight, scanRight1, slice, + sort, split, sum, tail, @@ -504,6 +505,15 @@ test("groupByIdentity", t => { ); }); +test("sort", t => { + t.deepEqual(sort([2, 4, 3, 1]), [1, 2, 3, 4]); + t.deepEqual(sort(["hello", "goodbye"]), ["goodbye", "hello"]); + t.deepEqual( + sort([-2, 4, -3, 1], (a, b) => Math.abs(a) - Math.abs(b)), + [1, -2, -3, 4] + ); +}); + test("forEach", t => { const a = ["a", "b", "c"]; let s = ""; diff --git a/index.ts b/index.ts index 5d28aceb..cc6db064 100644 --- a/index.ts +++ b/index.ts @@ -1512,6 +1512,18 @@ export function shuffle(array: ArrayLike): T[] { return result; } +export function sort(array: ArrayLike): boolean[]; +export function sort(array: ArrayLike): number[]; +export function sort(array: ArrayLike): string[]; +export function sort(array: ArrayLike, comparator: Comparator): T[]; +export function sort(array: ArrayLike, comparator?: Comparator): T[] { + return copy(array).sort(comparator ?? (defaultCompare as any)); +} + +export function sortFn(comparator: Comparator): (array: ArrayLike) => T[] { + return array => sort(array, comparator); +} + export function forEach( array: ArrayLike, f: (element: T, index: number) => void From 51d77817dade307ea64b8dfcc39ec2d249369731 Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 23:44:07 +0000 Subject: [PATCH 7/8] feat(sortby): add sortBy function --- index.test.ts | 25 +++++++++++++++++++++++++ index.ts | 13 +++++++++++++ 2 files changed, 38 insertions(+) diff --git a/index.test.ts b/index.test.ts index b9a5bafa..0ff41bb8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -49,6 +49,7 @@ import { scanRight1, slice, sort, + sortBy, split, sum, tail, @@ -514,6 +515,30 @@ test("sort", t => { ); }); +test("sortBy", t => { + t.deepEqual( + sortBy( + [ + {x: "a", y: 2}, + {x: "b", y: 4}, + {x: "c", y: 3}, + {x: "d", y: 1} + ], + ({y}) => y + ), + [ + {x: "d", y: 1}, + {x: "a", y: 2}, + {x: "c", y: 3}, + {x: "b", y: 4} + ] + ); + t.deepEqual( + sortBy([-2, 4, -3, 1], e => Math.abs(e)), + [1, -2, -3, 4] + ); +}); + test("forEach", t => { const a = ["a", "b", "c"]; let s = ""; diff --git a/index.ts b/index.ts index cc6db064..26caa8e4 100644 --- a/index.ts +++ b/index.ts @@ -1524,6 +1524,19 @@ export function sortFn(comparator: Comparator): (array: ArrayLike) => T return array => sort(array, comparator); } +export function sortBy(array: ArrayLike, select: SortSelect): T[] { + return sort(array, (a, b) => defaultCompare(select(a) as any, select(b) as any)); +} + +export type SortSelect = + | ((element: T) => boolean) + | ((element: T) => number) + | ((element: T) => string); + +export function sortByFn(select: SortSelect): (array: ArrayLike) => T[] { + return array => sortBy(array, select); +} + export function forEach( array: ArrayLike, f: (element: T, index: number) => void From 524b6700704ba1226b59d10121705a4646e7bf1f Mon Sep 17 00:00:00 2001 From: Daniel Cassidy Date: Thu, 13 Jan 2022 23:49:51 +0000 Subject: [PATCH 8/8] feat(sortbydescending): add sortByDescending function --- index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/index.ts b/index.ts index 26caa8e4..3a892f03 100644 --- a/index.ts +++ b/index.ts @@ -1528,15 +1528,23 @@ export function sortBy(array: ArrayLike, select: SortSelect): T[] { return sort(array, (a, b) => defaultCompare(select(a) as any, select(b) as any)); } +export function sortByFn(select: SortSelect): (array: ArrayLike) => T[] { + return array => sortBy(array, select); +} + +export function sortByDescending(array: ArrayLike, select: SortSelect): T[] { + return sort(array, (a, b) => -defaultCompare(select(a) as any, select(b) as any)); +} + +export function sortByDescendingFn(select: SortSelect): (array: ArrayLike) => T[] { + return array => sortByDescending(array, select); +} + export type SortSelect = | ((element: T) => boolean) | ((element: T) => number) | ((element: T) => string); -export function sortByFn(select: SortSelect): (array: ArrayLike) => T[] { - return array => sortBy(array, select); -} - export function forEach( array: ArrayLike, f: (element: T, index: number) => void