src/array.ts

Summary

Maintainability
D
2 days
Test Coverage
A
100%
import { Option } from "./option";
import { positiveNumber, truePredicate } from "./utils";

/**
 * `filterMap` filers and maps an iterable at the same time.
 *
 * It makes chains of `filter` and `map` more concise, as it shortens `map().filter().map()` to a single call.
 *
 * @param arr - An array
 * @param fn - A function that produces an `Option`.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns An array of filtered and mapped values.
 */
export const filterMap = <T, U>(
  arr: T[],
  fn: (value: T, index: number, array: T[]) => Option<U>,
  thisArg?: any
): U[] => {
  const results: U[] = [];
  for (let i = 0; i < arr.length; i++) {
    const result = fn.call(thisArg, arr[i], i, arr);
    if (result.isSome()) {
      results.push(result.unwrap());
    }
  }
  return results;
};

/**
 * `mapWhile` maps an iterable until the first `None` is encountered.
 *
 * @param arr - An array
 * @param fn - A function that produces an `Option`.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns An array of mapped values.
 */
export const mapWhile = <T, U>(
  arr: T[],
  fn: (value: T, index: number, array: T[]) => Option<U>,
  thisArg?: any
): U[] => {
  const results: U[] = [];
  for (let i = 0; i < arr.length; i++) {
    const result = fn.call(thisArg, arr[i], i, arr);
    if (result.isNone()) {
      break;
    }
    results.push(result.unwrap());
  }
  return results;
};

/**
 * `reduceWhile` reduces an iterable until the first `None` is encountered.
 *
 * @param arr - An array
 * @param fn - A function that produces an `Option`.
 * @param initialValue - The initial value.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns The reduced value.
 */
export const reduceWhile = <T, U = T>(
  arr: T[],
  fn: (
    previousValue: U,
    currentValue: T,
    currentIndex: number,
    array: T[]
  ) => Option<U>,
  initialValue: U,
  thisArg?: any
): U => {
  let acc = initialValue;
  for (let i = 0; i < arr.length; i++) {
    const result = fn.call(thisArg, acc, arr[i], i, arr);
    if (result.isNone()) {
      break;
    }
    arr.reduce;
    acc = result.unwrap();
  }
  return acc;
};

/**
 * Returns the index of the first element in the array that satisfies the provided testing function. Otherwise `None` is returned.
 *
 * @param arr - An array
 * @param predicate - A predicate function.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns The index of the first item that matches the predicate, or `None` if no item matches.
 */
export const firstIndex = <T>(
  arr: T[],
  predicate: (value: T, index: number, array: T[]) => boolean,
  thisArg?: any
): Option<number> =>
  Option.from(arr.findIndex(predicate, thisArg), positiveNumber);

/**
 * Returns the index of the last element in the array where predicate is true, and `None` otherwise.
 *
 * @param arr - An array
 * @param predicate - lastIndex calls predicate once for each element of the array, in backward order, until it finds one where predicate returns true.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns The index of the last item that matches the predicate, or `None` if no item matches.
 */
export const lastIndex = <T>(
  arr: T[],
  predicate: (value: T, index: number, array: T[]) => boolean,
  thisArg?: any
): Option<number> =>
  Option.from(arr.findLastIndex(predicate, thisArg), positiveNumber);

/**
 * `first` finds the first item that matches a predicate. Returns the first item of array if no predicate is provided.
 *
 * @param arr - An array
 * @param predicate - A predicate function.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, the first element is returned.
 * @returns The first item that matches the predicate, or `None` if no item matches.
 */
export const first = <T>(
  arr: T[],
  predicate: (value: T, index: number, array: T[]) => boolean = truePredicate,
  thisArg?: any
): Option<T> => firstIndex(arr, predicate, thisArg).map(valueAtIndex, arr);

/**
 * `last` finds the last item that matches a predicate. Returns the last item of array if no predicate is provided.
 *
 * @param arr - An array
 * @param predicate - A predicate function.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, the last element is returned.
 * @returns The last item that matches the predicate, or `None` if no item matches.
 */
export const last = <T>(
  arr: T[],
  predicate: (value: T, index: number, array: T[]) => boolean = truePredicate,
  thisArg?: any
): Option<T> => lastIndex(arr, predicate, thisArg).map(valueAtIndex, arr);

/**
 * Applies function to the elements of iterator and returns the first non-none result.
 *
 * `firstMap(fn)` is the lighter version of `filterMap(fn).first()`.
 *
 * @param arr - An array
 * @param fn - A function that produces an `Option`.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns The first non-none result.
 */
export const firstMap = <T, U>(
  arr: T[],
  fn: (value: T, index: number, array: T[]) => Option<U>,
  thisArg?: any
): Option<U> => {
  for (let i = 0; i < arr.length; i++) {
    const result = fn.call(thisArg, arr[i], i, arr);
    if (result.isSome()) {
      return result;
    }
  }
  return Option.None;
};

/**
 * `lastMap(fn)` is the lighter version of `filterMap(fn).last()`.
 *
 * @param arr - An array
 * @param fn - A function that produces an `Option`.
 * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
 * @returns The last non-none result.
 */
export const lastMap = <T, U>(
  arr: T[],
  fn: (value: T, index: number, array: T[]) => Option<U>,
  thisArg?: any
): Option<U> => {
  for (let i = arr.length - 1; i >= 0; i--) {
    const result = fn.call(thisArg, arr[i], i, arr);
    if (result.isSome()) {
      return result;
    }
  }
  return Option.None;
};

function valueAtIndex<T = any>(this: T[], index: number): T {
  return this[index];
}