1.2.0 • Published 5 years ago

list-ext v1.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 years ago

list-ext

Extends JavaScript arrays with additional functionality.

How does it work?

list-ext attaches additional methods to the Array prototype. This makes these functions available to all arrays. list-ext itself uses Ramda under the hood for most functions e.g. [1, 2].union([2, 3]) calls R.union([1, 2], [2, 3]). To provided safety list-ext uses Optionals as return values where a method may not succeed e.g. [].head will return None instead of throwing an exception.

Warning

This library extends the build-in array type. This can potentially break other libraries. So use this library with caution. If this library does not work with your project I suggest using list.

Installation

You can install 'list-ext' using NPM.

npm install --save list-ext

Example

import 'list-ext'

const delayedLength = (str: string) => new Promise<number>(resolve => {
    setTimeout(() => resolve(str.length), 1000)
})

/* Plain syntax without list-ext */
const plain = Promise.all(
    R.append('eeeee',
        R.union(['a', 'bb', 'ccc'], ['ccc', 'dddd'])
    ).map(x => delayedLength(x))
).then(x => x.take(2))

/* Ramda pipe syntax */
const pipe = Promise.all(
    R.pipe(
        R.union(['a', 'bb', 'ccc']),
        R.append('eeeee')
    )(['ccc', 'dddd'])
        .map(x => delayedLength(x))
).then(x => x.take(2))

/* list-ext syntax */
const listEx = ['a', 'bb', 'ccc']
    .union(['ccc', 'dddd'])
    .appending('eeeee')
    .mapAsync(x => delayedLength(x))
    .then(x => x.take(2))

Why use list-ext?

Working with lists using list-ext feels more JavaScripty compared to using ramda on itself.

Important Notice

When using NodeJS it is important to import 'list-ext' in each file you want to use it. If your using a bundler like e.g. Webpack it is enough to import it in the index file.

API Documentation

interface Array<T> {
    /**
     * Returns the first element. In some libraries this function is named `first`.
     * @example
     *
     *      ['fi', 'fo', 'fum'].head    //=> Some 'fi'
     *      [].head                     //=> None
     */
    head: Optional<T>

    /**
     * Returns all but the last element.
     * @example
     *
     *      [1, 2, 3].init   //=> [1, 2]
     *      [1, 2].init      //=> [1]
     *      [1].init         //=> []
     *      [].init          //=> []
     */
    init: T[]

    /**
     * Returns the last element.
     * @example
     *
     *      ['fi', 'fo', 'fum'].head    //=> Some 'fum'
     *      [].head                     //=> None
     */
    last: Optional<T>

    /**
     * Returns all but the first element.
     * @example
     *
     *      [1, 2, 3].init   //=> [2, 3]
     *      [1, 2].init      //=> [2]
     *      [1].init         //=> []
     *      [].init          //=> []
     */
    tail: T[]

    /**
     * Applies a function to the value at the given index, returning a new copy
     * of with the element at the given index replaced with the result of the
     * function application.
     *
     * @param index The index.
     * @param fn The function to apply.
     * @example
     *
     *      ['a', 'b', 'c', 'd'].adjust(1, R.toUpper)      //=> ['a', 'B', 'c', 'd']
     *      ['a', 'b', 'c', 'd'].adjust(-1, R.toUpper)     //=> ['a', 'b', 'c', 'D']
     */
    adjust(index: number, fn: (value: T) => T): T[]

    /**
     * Returns a new list, composed of n-tuples of consecutive elements. If `n` is
     * greater than the length of the list, an empty list is returned.
     *
     * @param n The size of the tuples to create
     * @example
     *
     *      [1, 2, 3, 4, 5].aperture(2) //=> [[1, 2], [2, 3], [3, 4], [4, 5]]
     *      [1, 2, 3, 4, 5].aperture(3) //=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
     *      [1, 2, 3, 4, 5].aperture(7) //=> []
     */
    aperture(n: number): T[][]

    /**
     * Returns a new list containing the contents of current list, followed by
     * the given element.
     *
     * @param elem The element to add to the end of the new list.
     * @example
     *
     *      ['write', 'more'].appending('tests')    //=> ['write', 'more', 'tests']
     *      [].appending('tests')                   //=> ['tests']
     *      ['write', 'more'].appending(['tests'])  //=> ['write', 'more', ['tests']]
     */
    appending(elem: T): T[]

    /**
     * Returns the result of concatenating the current list with the given lists.
     *
     * @param other The second list
     * @example
     *
     *      [4, 5, 6].appendingAll([1, 2, 3]) //=> [4, 5, 6, 1, 2, 3]
     *      [].appendingAll([])               //=> []
     */
    appendingAll(other: T[]): T[]

    /**
     * `chain` maps a function over the current list and concatenates the results.
     * `chain` is also known as `flatMap` in some libraries.
     *
     * @param fn The function to map with
     * @example
     *
     *      const duplicate = n => [n, n]

     *      [1, 2, 3].chain(duplicate) //=> [1, 1, 2, 2, 3, 3]
     */
    chain<R>(fn: (value: T) => R[]): R[]

    /**
     * Returns all but the first `n` elements.
     *
     * @param n
     * @example
     *
     *      ['foo', 'bar', 'baz'].drop(1) //=> ['bar', 'baz']
     *      ['foo', 'bar', 'baz'].drop(2) //=> ['baz']
     *      ['foo', 'bar', 'baz'].drop(3) //=> []
     *      ['foo', 'bar', 'baz'].drop(4) //=> []
     */
    drop(n: number): T[]

    /**
     * Returns all but the last `n` elements.
     *
     * @param {Number} n
     * @example
     *
     *      ['foo', 'bar', 'baz'].drop(1) //=> ['foo', 'bar']
     *      ['foo', 'bar', 'baz'].drop(2) //=> ['foo']
     *      ['foo', 'bar', 'baz'].drop(3) //=> []
     *      ['foo', 'bar', 'baz'].drop(4) //=> []
     */
    dropLast(n: number): T[]

    /**
     * Returns a new list without any consecutively repeating elements.
     *
     * @example
     *
     *     [1, 1, 1, 2, 3, 4, 4, 2, 2].dropRepeats() //=> [1, 2, 3, 4, 2]
     */
    dropRepeats(): T[]

    /**
     * Checks if list ends with the provided sublist.
     *
     * @param {*} suffix
     * @example
     *
     *      ['a', 'b', 'c'].endsWith(['c'])    //=> true
     *      ['a', 'b', 'c'].endsWith('c')      //=> true
     *      ['a', 'b', 'c'].endsWith(['b'])    //=> false
     *      ['a', 'b', 'c'].endsWith('b')      //=> false
     */
    endsWith(suffix: T | T[]): boolean

    /**
     * Returns the first element of the list which matches the predicate, or
     * Error if no element matches.
     *
     * @param fn The predicate function used to determine if the element is the
     *        desired one.
     * @example
     *
     *      const xs = [{a: 1}, {a: 2}, {a: 3}]

     *      xs.find(R.propEq('a', 2)) //=> Ok {a: 2}
     *      xs.find(R.propEq('b', 2)) //=> Error
     */
    tryFind(fn: (elem: T) => boolean): Optional<T>

    /**
     * Returns the last element of the list which matches the predicate, or
     * Error if no element matches.
     *
     * @param fn The predicate function used to determine if the element is the
     *        desired one.
     * @example
     *
     *      const xs = [{a: 1}, {a: 2}, {a: 2}]

     *      xs.find(R.propEq('a', 2)) //=> Ok {a: 2}
     *      xs.find(R.propEq('b', 2)) //=> Error
     */
    tryFindLast(fn: (elem: T) => boolean): Optional<T>

    /**
     * Returns a promise of a filtered list containing only the elements for
     * which the provided function yields true.
     *
     * @param fn Asynchronous filter function
     * @example
     *
     *      ['/root/test.txt', '/root/test2.txt'].filter(x => fs.pathExists(x)) //=> Promise ['/root/test.txt']
     */
    filterAsync(fn: (elem: T) => Promise<boolean>): Promise<T[]>

    /**
     * Run asynchronous action for each element in list in sequents and not in parallel.
     * @param fn Asynchronous action to run for each element
     * @example
     *      [1, 2, 3].forEachSync(async elem =>
     *          const res = await addOneDelayed(elem)
     *          console.log(res)
     *      )
     *      //=> 2
     *      //=> 3
     *      //=> 4
     */
    forEachSync(fn: (elem: T, index: number) => Promise<void>): Promise<void>

    /**
     * Splits a list into sub-lists stored in an object, based on the result of
     * calling a String-returning function on each element, and grouping the
     * results according to values returned.
     *
     * @param fn Function :: a -> String
     * @example
     *
     *      const students = [
     *          { name: 'Abby', score: 84 },
     *          { name: 'Eddy', score: 58 },
     *          { name: 'Jack', score: 69 }
     *      ]
     *
     *      students.groupBy(({ score }) =>
     *          score < 65 ? 'F' :
     *          score < 70 ? 'D' :
     *          score < 80 ? 'C' :
     *          score < 90 ? 'B' : 'A'
     *      )
     *      // {
     *      //   'A': [{name: 'Dianne', score: 99}],
     *      //   'B': [{name: 'Abby', score: 84}]
     *      //   // ...,
     *      //   'F': [{name: 'Eddy', score: 58}]
     *      // }
     */
    groupBy(fn: (elem: T) => string): {
        [key: string]: T[]

    }

    /**
     * Returns a list of lists where each sublist's elements are all satisfied
     * pairwise comparison according to the provided function.
     * Only adjacent elements are passed to the comparison function.
     *
     * @param fn Function for determining whether two given (adjacent)
     *        elements should be in the same group
     * @example
     *
     * [0, 1, 1, 2, 3, 5, 8, 13, 21].groupWith((a, b) => a + 1 === b)
     * //=> [[0, 1], [1, 2, 3], [5], [8], [13], [21]]
     *
     * [0, 1, 1, 2, 3, 5, 8, 13, 21].groupWith((a, b) => a % 2 === b % 2)
     * //=> [[0], [1, 1], [2], [3, 5], [8], [13, 21]]
     */
    groupWith(fn: (a: T, b: T) => boolean): T[][]

    /**
     * Returns `true` if the specified value is equal, in [`R.equals`](#equals)
     * terms, to at least one element
 `false` otherwise.
     *
     * @param elem The item to compare against.
     * @example
     *
     *      [1, 2, 3].includes(3) //=> true
     *      [1, 2, 3].includes(4) //=> false
     *      [{ name: 'Fred' }].includes({ name: 'Fred' }) //=> true
     *      [[42]].includes([42])
 //=> true
     */
    includes(elem: T): boolean

    /**
     * Inserts the supplied element into the list, at the specified `index`. _Note that
     * this is not destructive_: it returns a copy of the list with the changes.
     * <small>No lists have been harmed in the application of this function.</small>
     *
     * @param index The position to insert the element
     * @param value The element to insert into the Array
     * @example
     *
     *      [1, 2, 3, 4].insert(2, 5) //=> [1, 2, 5, 3, 4]
     */
    insert(index: number, value: T): T[]

    /**
     * Inserts the sub-list into the list, at the specified `index`. _Note that this is not
     * destructive_: it returns a copy of the list with the changes.
     * <small>No lists have been harmed in the application of this function.</small>
     *
     * @param index The position to insert the sub-list
     * @param values The sub-list to insert into the Array
     * @example
     *
     *      [1, 2, 3, 4].insertAll(2, [7, 8, 9]) //=> [1, 2, 7, 8, 9, 3, 4]
     */
    insertAll(index: number, values: T[]): T[]

    /**
     * Creates a new list with the separator interposed between elements.
     *
     * @param separator The element to add to the list.
     * @example
     *
     *      ['b', 'n', 'n', 's'].intersperse('a') //=> ['b', 'a', 'n', 'a', 'n', 'a', 's']
     */
    intersperse(elem: T): T[]

    /**
     * Apply function to each element of the list and remove resulting empty
     * values (undefined, null, '', [], Error, None).
     *
     * @param {Function} fn The function to be called on every element of the input `list`.
     * @example
     *
     *      [1, 2, 3].mapNonEmpty(x => x === 2 ? undefined : x + 1) //=> [2, 4]
     */
    mapNonEmpty<R>(f: (elem: T, index: number) => Result<R> | Optional<R> | R): R[]

    /**
     * Apply asynchronous function to each element of the list and remove resulting empty
     * values (undefined, null, '', [], Error, None).
     *
     * @param {Function} fn The asynchronous function to be called on every element of the input `list`.
     * @example
     *
     *      [1, 2, 3].mapAsyncNonEmpty(x => x === 2 ? Promise.resolve(undefined) : addOneDelayed(x)) //=> Promise [2, 4]
     */
    mapAsyncNonEmpty<R>(f: (elem: T, index: number) => Promise<Result<R> | Optional<R> | R>): Promise<R[]>

    /**
     * Apply asynchronous function to each element of the list and return a list promise.
     *
     * @param {Function} fn The asynchronous function to be called on every element of the input `list`.
     * @example
     *
     *      [1, 2, 3].mapAsync(x => addOneDelayed(x)) //=> Promise [2, 3, 4]
     */
    mapAsync<R>(f: (elem: T, index: number) => Promise<R>): Promise<R[]>

    /**
     * Apply asynchronous function to each element of the list in sequence and return a list promise.
     *
     * @param {Function} fn The asynchronous function to be called on every element of the input `list`.
     * @example
     *
     *      [1, 2, 3].mapSync(x => addOneDelayed(x)) //=> Promise [2, 3, 4]
     */
    mapSync<R>(nf: (elem: T, index: number) => Promise<R>): Promise<R[]>

    /**
     * Returns the nth element of the list. If n is negative the element at
     * index length + n is returned.
     *
     * @param offset
     * @example
     *
     *      const list = ['foo', 'bar', 'baz', 'quux']

     *      list.nth(1) //=> Some 'bar'
     *      list.nth(-1) //=> Some 'quux'
     *      list.nth(-99) //=> None
     */
    nth(offset: number): Optional<T>

    /**
     * Takes a predicate and returns the pair of the same type of elements which do and do not
     * satisfy, the predicate, respectively.
     *
     * @param fn A predicate to determine which side the element belongs to.
     * @example
     *
     *      ['sss', 'ttt', 'foo', 'bars'].partition(x => x.includes('s'))
     *      // => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]
     */
    partition(fn: (elem: T) => boolean): [T[], T[]]

    /**
     * Returns a new list with the given element at the front, followed by the
     * contents of the list.
     *
     * @param value The item to add to the head of the output list.
     * @example
     *
     *      ['fi', 'fo', 'fum'].prepend('fee') //=> ['fee', 'fi', 'fo', 'fum']
     */
    prepend(value: T): T[]

    /**
     * The complement of filter.
     *
     * @param fn Filter function
     * @example
     *
     *      [1, 2, 3, 4].reject(n => n % 2 === 1) //=> [2, 4]
     */
    reject(fn: (elem: T) => boolean): T[]

    /**
     * Removes element from list.
     *
     * @param elem Element to remove
     * @example
     *
     *      [1, 2, 3, 4, 5, 2].remove(2) //=> [1, 3, 4, 5]
     */
    remove(elem: T): T[]

    /**
     * Splits list at a given index.
     *
     * @param index The index where the list is split.
     * @example
     *
     *      [1, 2, 3].splitAt(1) //=> [[1], [2, 3]]
     */
    splitAt(index: number): T[][]

    /**
     * Splits list into slices of the specified length.
     *
     * @param n Slice length
     * @example
     *
     *      [1, 2, 3, 4, 5, 6, 7].splitEvery(3) //=> [[1, 2, 3], [4, 5, 6], [7]]
     */
    splitEvery(n: number): T[][]

    /**
     * Splits list into `n` slices.
     *
     * @param n Slice length
     * @example
     *
     *      [1, 2, 3, 4, 5, 6, 7].splitInto(2) //=> [ [ 1, 2, 3, 4 ], [ 5, 6, 7 ] ]
     */
    splitInto(n: number): T[][]

    /**
     * Returns a pair of lists with the following properties:
     *
     *  - the result of concatenating the two output lists is equivalent to the input list

     *  - none of the elements of the first output list satisfies the predicate
 and
     *  - if the second output list is non-empty, its first element satisfies the predicate.
     *
     * @param fn The predicate that determines where the array is split.
     * @param {Array} list The array to be split.
     * @return {Array}
     * @example
     *
     *      [1, 2, 3, 1, 2, 3].splitWhen(x => x === 2)   //=> [[1], [2, 3, 1, 2, 3]]
     */
    splitWhen(fn: (elem: T) => boolean): T[][]

    /**
     * Checks if list starts with the provided sublist.
     *
     * @param prefix
     * @example
     *
     *      ['a', 'b', 'c'].startsWith(['a'])    //=> true
     *      ['a', 'b', 'c'].startsWith(['b'])    //=> false
     */
    startsWith(prefix: T | T[]): boolean

    /**
     * Returns a new list containing the first `n` elements.
     *
     * @param n The number of elements to return.
     * @example
     *
     *      ['foo', 'bar', 'baz'].take(1) //=> ['foo']
     *      ['foo', 'bar', 'baz'].take(2) //=> ['foo', 'bar']
     *      ['foo', 'bar', 'baz'].take(3) //=> ['foo', 'bar', 'baz']
     *      ['foo', 'bar', 'baz'].take(4) //=> ['foo', 'bar', 'baz']
     */
    take(n: number): T[]

    /**
     * Returns a new list containing the last `n` elements.
     *
     * @param n The number of elements to return.
     * @example
     *
     *      ['foo', 'bar', 'baz'].takeLast(1) //=> ['baz']
     *      ['foo', 'bar', 'baz'].takeLast(2) //=> ['bar', 'baz']
     *      ['foo', 'bar', 'baz'].takeLast(3) //=> ['foo', 'bar', 'baz']
     *      ['foo', 'bar', 'baz'].takeLast(4) //=> ['foo', 'bar', 'baz']
     */
    takeLast(n: number): T[]

    /**
     * Returns a new list containing the first `n` elements of the list,
     * passing each value to the supplied predicate function, and terminating when
     * the predicate function returns `false`. Excludes the element that caused the
     * predicate function to fail.
     *
     * @param fn The function called per iteration.
     * @example
     *
     *       [1, 2, 3, 4, 3, 2, 1].takeWhile(x => x !== 4) //=> [1, 2, 3]
     */
    takeWhile(fn: (elem: T) => boolean): T[]

    /**
     * Returns a new list containing the last `n` elements of the list, passing
     * each value to the supplied predicate function, and terminating when the
     * predicate function returns `false`. Excludes the element that caused the
     * predicate function to fail.
     *
     * @param fn The function called per iteration.
     * @example
     *
     *      [1, 2, 3, 4].takeLastWhile(x => x !== 1)
 //=> [2, 3, 4]
     */
    takeLastWhile(fn: (elem: T) => boolean): T[]

    /**
     * Combines current lists with other list into a set (i.e. no duplicates) composed
     * of the elements of each list.
     *
     * @param other The second list.
     * @example
     *
     *      [1, 2, 3].union([2, 3, 4]) //=> [1, 2, 3, 4]
     */
    union(other: T[]): T[]

    /**
     * Returns a new list containing only one copy of each element.
     *
     * @example
     *
     *      [1, 1, 2, 1].unique() //=> [1, 2]
     *      [1, '1'].unique()     //=> [1, '1']
     *      [[42], [42]].unique() //=> [[42]]
     */
    unique(): T[]

    /**
     * Returns a new list containing only one copy of each element, based upon
     * the value returned by applying the supplied function to  each list element.
     * Prefers the first item if the supplied function produces the same value on
     * two items.
     *
     * @param fn A function used to produce a value to use during comparisons.
     * @example
     *
     *      [-1, -5, 2, 10, 1, 2].uniqueBy(Math.abs) //=> [-1, -5, 2, 10]
     */
    uniqueBy<R>(fn: (elem: T) => R): T[]

    /**
     * Returns a new copy of the list with the element at the provided index
     * replaced with the given value.
     *
     * @param index The index to update.
     * @param value The value to exist at the given index of the returned array.
     * @example
     *
     *      ['a', 'b', 'c'].update(1, '_')      //=> ['a', '_', 'c']
     *      ['a', 'b', 'c'].update(-1, '_')     //=> ['a', 'b', '_']
     */
    update(index: number, value: T): T[]

    /**
     * Returns a new list without values in the first argument.
     *
     * @param values The values to be removed.
     * @example
     *
     *      [1, 2, 1, 3, 4].without([1, 2]) //=> [3, 4]
     */
    without(values: T[]): T[]

    /**
     * Returns a new list without `empty` values (undefined, null, '', [], Error, None).
     *
     * @example
     *
     *      [1, null, undefined, 3, [], Optional.none()].withoutEmpty() //=> [1, 3]
     */
    withoutEmpty(): T[]

    /**
     * Creates a new list out of this and a supplied list by creating each
     * possible pair from the lists.
     *
     * @param other Other list.
     * @example
     *
     *      [1, 2].xprod(['a', 'b']) //=> [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
     */
    xprod<R>(other: R[]): [T, R][]

    /**
     * Creates a new list out of this and a supplied list by pairing up equally-positioned
     * items from both lists. The returned list is truncated to the length of the
     * shorter of the two input lists.
     *
     * @param other The second array to consider.
     * @example
     *
     *      [1, 2, 3].zip(['a', 'b', 'c']) //=> [[1, 'a'], [2, 'b'], [3, 'c']]
     */
    zip<S>(other: S[]): [T, S][]

    /**
     * Creates a new list out of this and a supplied list by applying the function to each
     * equally-positioned pair in the lists. The returned list is truncated to the
     * length of the shorter of the two input lists.
     *
     * @param other The second array to consider.
     * @param fn The function used to combine the two elements into one value.
     * @example
     *
     *      const f = (x, y) => //...
     *      [1, 2, 3].zipWith(['a', 'b', 'c'], f)
     *      //=> [f(1, 'a'), f(2, 'b'), f(3, 'c')]
     */
    zipWith<S, R>(other: S[], fn: (a: T, b: S) => R): R[]

    /**
     * Creates a new list out of this by pairing up the items with their index.
     *
     * @param other The second array to consider.
     * @example
     *
     *      ['a', 'b', 'c'].zipWithIndex() //=> [[0, 'a'], [1, 'b'], [2, 'c']]
     */
    zipWithIndex(): [number, T][]

}
1.2.0

5 years ago

1.1.5

5 years ago

1.1.2

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago