diff --git a/index.test.ts b/index.test.ts index 84f3a297..0ff41bb8 100644 --- a/index.test.ts +++ b/index.test.ts @@ -9,6 +9,7 @@ import { contains, dropWhile, empty, + equal, exclude, excludeFirst, excludeNull, @@ -34,6 +35,7 @@ import { mapKeyFirstBy, maximum, minimum, + only, or, partition, partitionWhile, @@ -46,6 +48,8 @@ import { scanRight, scanRight1, slice, + sort, + sortBy, split, sum, tail, @@ -85,6 +89,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])); @@ -120,6 +130,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), @@ -450,6 +492,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)), @@ -457,6 +506,39 @@ 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("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 = ""; @@ -464,10 +546,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"]); -}); diff --git a/index.ts b/index.ts index b201bbd3..599ccd01 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; } @@ -182,6 +188,50 @@ 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); +} + +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; +} + // 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[]; @@ -792,9 +842,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; } @@ -1463,6 +1514,39 @@ 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 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 forEach( array: ArrayLike, f: (element: T, index: number) => void