list-fns v1.4.0
list-fns
This library contains higher order functions that simplify common list operations, similar to what you'd find in lodash or ramda. Unlike these libraries, list-fns is designed specifically to be used with the native array methods.
These functions have not been rigorously tested for performance so they are currently not recommended for use with large datasets.
Example
import { byProperty, get, uniqueByProperty } from "list-fns";
const people = [
{ name: "Jack", age: 44 },
{ name: "Jack", age: 60 },
{ name: "Jane", age: 20 },
];
// Inline implementation:
people
.filter(
(person, index) => index === people.findIndex(p => p.name === person.name)
)
.sort((a, b) => (a.age < b.age ? -1 : a.age > b.age ? 1 : 0))
.map(person => person.name); // ["Jane", "Jack"]
// With list functions:
people
.filter(uniqueByProperty("name"))
.sort(byProperty("age"))
.map(get("name")); // ["Jane", "Jack"]Install
npm install list-fnsA note about sorting
This library contains functions to be used with [].sort(). Always be mindful of the fact that .sort() and .reverse() will mutate the original list. If .sort() is the first method you're calling on a list you should probably clone it first in order to avoid unexpected behavior:
[...list].sort();
list.slice().sort();
[].concat(list).sort();Functions
by
by: <T>(func: (el: T) => any) => (a: T, b: T) => 0 | 1 | -1Use with: sort
Sort the elements by func(element) . Supports sorting by boolean values (elements that are true first).
[{ a: 2 }, { a: 1 }].sort(by(el => el.a)); // Returns [{ a: 1 }, { a: 2 }]byProperty
byProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (a: TObject, b: TObject) => 0 | 1 | -1Use with: sort
Sort the elements by element[key] (can also be an array index). Supports sorting by boolean values (elements that are true first).
[{ a: 2 }, { a: 1 }].sort(byProperty('a')); // Returns [{ a: 1 }, { a: 2 }]
[["a", 2], ["a", 1]].sort(byProperty(1)); // Returns [["a", 1], ["a", 2]]byValue
byValue: (a: number, b: number) => 0 | 1 | -1Use with: sort
Sort a list of numbers. This is useful because javascript sorts numbers as string, meaning that 25, 100 results in 100, 25 since "2" is greater than "1"
[100, 25].sort(); // Returns [100, 25]
[100, 25].sort(byValue); // Returns [25, 100]countBy
countBy: <T>(func: (el: T) => boolean) => (acc: number, el: T) => numberUse with: reduce
Returns the number of times func returned true for the list elements. A number must be passed to the second argument of reduce . Can be combined with boolean-returning functions like is , isnt , propertyIs or propertyIsOneOf .
["a", "a", "b"].reduce(countBy(el => el === "a"), 0); // Returns 2
["a", "a", "b"].reduce(countBy(is("a")), 0); // Returns 2duplicates
duplicates: (el: unknown, _: number, list: unknown[]) => booleanUse with: filter
Returns duplicates
[1, 1, 1, 2].filter(duplicates); // Returns [1, 1, 1]duplicatesBy
duplicatesBy: <T>(func: (el: T) => unknown) => (el: T, _: number, list: T[]) => booleanUse with: filter
Returns all duplicates compared by func(element)
[{ a: 1 }, { a : 1 }, { a: 2 }].filter(duplicatesBy(el => el.a)); // Returns [{ a: 1 }, { a: 1 }]duplicatesByProperty
duplicatesByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (el: TObject, _: number, list: TObject[]) => booleanUse with: filter
Returns duplicates compared by element[key]
[{ a: 1 }, { a: 1 }].filter(duplicatesByProperty('a')); // Return [{ a: 1 }, { a: 1 }]exclude
exclude: <T>(list: T[]) => (el: T) => booleanUse with: filter
Removes the provided elements from the list
[1, 2, 3, 4].filter(exclude([1, 2])); // Returns [3, 4]excludeBy
excludeBy: <T>(func: (el: T) => unknown, list: T[]) => (el: T) => booleanUse with: filter
Removes the provided elements from the list compared by running func on elements in both lists
[{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
.filter(excludeBy(el => el.a, [{ a: 1 }, { a: 2 }]));
// Returns [{ a: 3 }, { a: 4 }]excludeByProperty
excludeByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[]) => (el: TObject) => booleanUse with: filter
Removes the provided elements from the list compared at key
[{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
.filter(excludeByProperty('a', [{ a: 1 }, { a: 2 }]));
// Returns [{ a: 3 }, { a: 4 }]get
get: {
<TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2]>(key1: TKey1, key2: TKey2, key3: TKey3): (obj: TObject) => TObject[TKey1][TKey2][TKey3];
<TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1]>(key1...;
}Use with map or filter
Returns element[key] (can also be an array index). Supports up to three keys of depth.
[{ a: 1 }, { a: 2 }].map(get('a')); // Returns [1, 2]
[["a", 1], ["a", 2]].map(get(1)); // Returns [1, 2]
[{ a: { b: { c: 1 } } }].map(get('a', 'b', 'c')); // Returns [1]groupBy
groupBy: <K extends string, V>(func: (el: V) => K | undefined) => (acc: Record<K, V[]>, el: V) => Record<K, V[]>Use with: reduce
Given a key-returning function, returns the elements grouped in an object according to the returned keys. A second argument must be passed to reduce . For javascript an empty object is enough. For typescript an object with properties or a type cast may be required.
[{ age: 10 }, { age: 80 }].reduce(
groupBy(el => (el.age > 30 ? "old" : "young")),
{ old: [], young: [] }
); // Returns { old: [{ age: 80 }], young: [{ age: 10 }]}groupByMany
groupByMany: <K extends string, V>(func: (el: V) => K[] | undefined) => (acc: Record<K, V[]>, el: V) => Record<K, V[]>Use with: reduce
Given a function func that returns a list of keys, returns an object containing the elements grouped by the returned keys. Unlike the groupBy function, elements can appear several times in this object. Good for grouping objects by properties that are arrays. An empty object must be passed as the second argument to reduce
const b1: B = { items: ["a", "b"] };
const b2: B = { items: ["a"] };
[b1, b2].reduce(groupByMany(b => b.items), {});
// Returns { a: [{ items: ["a", "b"] }, { items: ["a"] }], b: [{ items: ["b"] }] }groupByProperty
groupByProperty: <K extends keyof V, V extends { [key: string]: any; }>(key: K) => (acc: Record<V[K], V[]>, el: V) => Record<V[K], V[]>Use with: reduce
Given a property name, returns an object containing the elements grouped by the values for that property. A second argument must be passed to reduce . For javascript an empty object is enough. For typescript an object with properties or a type cast may be required.
[{ name: "Jane" }, { name: "John" }].reduce(
groupByProperty("name"),
{}
); // Returns { Jane: [{ name: "Jane" }], John: [{ name: "John" }] }has
has: <TObject extends object, TKey extends keyof TObject>(...keys: TKey[]) => (object: TObject) => object is TObject & HasProperties<TObject, TKey>Use with: find , filter
Returns true for elements where element[key] for all provided keys is defined. This is useful when properties are needed but optional in the element type.
Known limitations: Type inference doesn't always work when list elements have an inferred type.
type Person = { name?: string };
const people: Person[] = [{ name: "John" }, {}];
people.filter(has("name")); // Returns [{ name: "a" }]intersection
intersection: <T>(list: T[]) => (el: T) => booleanUse with: filter
Returns a list of elements that are present in both lists
[1, 2, 3].filter(intersection([2, 3, 4])); // Returns [2, 3]intersectionBy
intersectionBy: <T>(func: (el: T) => unknown, list: T[]) => (el: T) => booleanUse with: filter
Returns a list of elements that are present in both lists compared by running func on elements in both lists
[{ a: 1 }, { a: 2 }, { a: 3 }]
.filter(intersectionBy(el => el.a, [{ a: 2 }, { a: 3 }, { a: 4 }]));
// Returns [{ a: 2 }, { a: 3 }]intersectionByProperty
intersectionByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[]) => (el: TObject) => booleanUse with: filter
Returns a list of elements that are present in both lists compared at key
[{ a: 1 }, { a: 2 }, { a: 3 }]
.filter(intersectionByProperty("a", [{ a: 2 }, { a: 3 }, { a: 4 }]));
// Returns [{ a: 2 }, { a: 3 }]is
is: <T>(value: T) => (el: T) => booleanUse with: find , filter
Returns true for elements that are equal to value
[1,2,3].find(is(1)); // Returns 1
[1,1,2].filter(is(1)); // Returns [1, 1]isBy
isBy: <T, U>(func: (el: T) => U, value: U) => (el: T) => booleanUse with: find , filter
Returns true for elements where func(element) equals value
[{ a: 1 }, { a: 2 }].find(isBy(el => el.a, 2)); // Returns { a: 2 }
[{ a: 1 }, { a: 2 }].filter(isBy(el => el.a, 2)); // Returns [{ a: 2 }]isDefined
isDefined: <T>(x: T) => x is NonNullable<T>Use with: filter
Remove elements that are undefined or null
[1, null, undefined, 2].filter(isDefined); // Returns [1, 2]isOneOf
isOneOf: <T>(list: T[]) => (el: T) => booleanUse with: find , filter
Alias for intersection . Returns true for elements that exist in the provided list
[1,1,2,2,3].filter(isOneOf([2,3])); // Returns [2, 2, 3]isOneOfBy
isOneOfBy: <T, U>(func: (el: T) => U, list: U[]) => (el: T) => booleanUse with: find , filter
Returns true for elements where func(element) exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(isOneOfBy(el => el.a, [2, 3]));
// ^ Returns { a: 2 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(isOneOfBy(el => el.a, [2, 3]));
// ^ Returns [{ a: 2 }, { a: 3 }]isnt
isnt: <T>(value: T) => (el: T) => booleanUse with: find , filter
Returns true for elements that are not equal to value
[1,2,3].find(isnt(1)); // Returns 2
[1,2,2].filter(isnt(1)); // Returns [2,2]isntBy
isntBy: <T, U>(func: (el: T) => U, value: U) => (el: T) => booleanUse with: find , filter
Returns true for elements where func(element) does not equal value
[{ a: 1 }, { a: 2 }].find(isntBy(el => el.a, 2)); // Returns { a: 1 }
[{ a: 1 }, { a: 2 }].filter(isntBy(el => el.a, 2)); // Returns [{ a: 1 }]isntOneOf
isntOneOf: <T>(list: T[]) => (el: T) => booleanUse with: find , filter
Alias for exclude . Returns true for elements that do not exist in the provided list
[1,1,2,2,3].filter(isntOneOf([2,3])); // Returns [1, 1]isntOneOfBy
isntOneOfBy: <T, U>(func: (el: T) => U, list: U[]) => (el: T) => booleanUse with: find , filter
Returns true for elements where func(element) exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(isntOneOfBy(el => el.a, [2, 3]));
// ^ Returns { a: 1 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(isntOneOfBy(el => el.a, [2, 3]));
// ^ Returns [{ a: 1 }]max
max: (acc: number, el: number) => numberUse with: reduce
Returns the largest value in the list
[1,2,3,4].reduce(max); // Returns 4maxBy
maxBy: <T>(func: (el: T) => number) => (acc: T, el: T) => TUse with: reduce
Returns the largest element by comparing func(element)
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(maxBy(el => el.a)); // Returns { a: 3 }maxByProperty
maxByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (acc: TObject, el: TObject) => TObjectUse with: reduce
Returns the largest element by comparing element[key]
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(maxByProperty("a")); // Returns { a: 3 }min
min: (acc: number, el: number) => numberUse with: reduce
Returns the smallest value in the list
[1,2,3,4].reduce(min); // Returns 1minBy
minBy: <T>(func: (el: T) => number) => (acc: T, el: T) => TUse with: reduce
Returns the smallest element by comparing func(element)
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(minBy(el => el.a)); // Returns { a: 1 }minByProperty
minByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (acc: TObject, el: TObject) => TObjectUse with: reduce
Returns the smallest element by comparing element[key]
[{ a: 1 }, { a: 2 }, { a: 3 }].reduce(minByProperty("a")); // Returns { a: 1 }or
or: <T>(fallback: NonNullable<T>) => (x: T) => NonNullable<T>Use with: map
Replaces list elements that are undefined or null with fallback
[1, null, undefined, 2].map(or(0)); // Returns [1, 0, 0, 2]partition
partition: <T>(func: (el: T) => boolean) => (acc: T[][], el: T) => T[][]Use with: reduce
Splits the input list into two lists. The first list contains elements for which the given function returned true , the second contains elements for which the function returned false .
[{ age: 10 }, { age: 80 }].reduce(partition(el => el.age > 30), []);
// Returns [[{ age: 80 }], [{ age: 10 }]]propertyIs
propertyIs: <TObject extends object, TKey extends keyof TObject>(key: TKey, value: TObject[TKey]) => (el: TObject) => booleanUse with: find , filter
Returns true for elements where element[key] equals value
[{ a: 1 }, { a: 2 }].find(propertyIs("a", 2)); // Returns { a: 2 }
[{ a: 1 }, { a: 2 }].filter(propertyIs("a", 2)) // Returns [{ a: 2 }]propertyIsOneOf
propertyIsOneOf: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[TKey][]) => (el: TObject) => booleanUse with: find , filter
Returns true for elements where element[key] exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(propertyIsOneOf("a", [2, 3]));
// ^ Returns { a: 2 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(propertyIsOneOf("a", [2, 3]));
// ^ Returns [{ a: 2 }, { a: 3 }]propertyIsnt
propertyIsnt: <TObject extends object, TKey extends keyof TObject>(key: TKey, value: TObject[TKey]) => (el: TObject) => booleanUse with: find , filter
Returns true for elements where element[key] does not equal value
[{ a: 1 }, { a: 2 }].find(propertyIsnt("a", 2)); // Returns { a: 1 }
[{ a: 1 }, { a: 2 }].filter(propertyIsnt("a", 2)); // Returns [{ a: 1 }]propertyIsntOneOf
propertyIsntOneOf: <TObject extends object, TKey extends keyof TObject>(key: TKey, list: TObject[TKey][]) => (el: TObject) => booleanUse with: find , filter
Returns true for elements where element[key] exists in list
[{ a: 1 }, { a: 2 }, { a: 3 }].find(propertyIsntOneOf("a", [2, 3]));
// ^ Returns { a: 1 }
[{ a: 1 }, { a: 2 }, { a: 3 }].filter(propertyIsntOneOf("a", [2, 3]));
// ^ Returns [{ a: 1 }]sum
sum: (acc: number, element: number) => numberUse with: reduce
Sum a list of numbers
[1, 2, 3].reduce(sum); // Returns 6sumBy
sumBy: {
<T>(func: (el: T) => number): (acc: number, el: T) => number;
<T>(func: (el: number) => number): (acc: number, el: number) => number;
}Use with: reduce
Sums the values by applying func to elements. If the list elements aren't numbers, a number must be passed as the second argument to reduce .
[{ a: 1 }, { a: 2 }].reduce(sumBy(el => el.a), 0); // Returns 3
[1.5, 2.5].reduce(sumBy(Math.floor)); // Returns 3sumByProperty
sumByProperty: <TObject extends { [key: string]: number; }, TKey extends keyof TObject>(key: TKey) => (acc: number, el: TObject) => numberUse with: reduce
Sums the values of element[key] for all elements. A number must be passed to the second argument of reduce .
[{ a: 1 }, { a: 2 }].reduce(sumByProperty('a'), 0); // Returns 3unique
unique: (el: unknown, index: number, list: unknown[]) => booleanUse with: filter
Removes duplicates from list
[1,1,1,2].filter(unique); // Returns [1, 2]uniqueBy
uniqueBy: <T>(func: (el: T) => unknown) => (el: T, index: number, list: T[]) => booleanUse with: filter
Removes duplicates compared by func(element)
[{ a: 1 }, { a : 1 }].filter(uniqueBy(el => el.a)); // Returns [{ a: 1 }]uniqueByProperty
uniqueByProperty: <TObject extends object, TKey extends keyof TObject>(key: TKey) => (el: TObject, index: number, list: TObject[]) => booleanUse with: filter
Removes duplicates compared by element[key]
[{ a: 1 }, { a: 1 }].filter(uniqueByProperty('a')); // Return [{ a: 1 }]