shortcuttedReduce.ts

import * as _ from 'lodash'
import { ValueOf } from './types'

export default shortcuttedReduce

function shortcuttedReduce<
  Collection extends object,
  CollectionKey extends keyof Collection,
  CollectionItem extends ValueOf<Collection>,
  Accumulator
> (
  collection: Collection,
  iteratee: (accum: Accumulator, item: CollectionItem, index: CollectionKey) => Accumulator,
  accumulator: Accumulator
): Accumulator

/**
 * Reduce which finish as soon as accumulator changes.
 *
 * @param collection Collection to be reduced.
 * @param iteratee Function that will be called in each iteration.
 * It'll receive as parameters the accumulator, the value and the index.
 * @param accumulator Note that is **must** be a simple primitive.
 * @returns Accumulated value.
 */
function shortcuttedReduce<
  CollectionItem,
  Collection extends ArrayLike<CollectionItem>,
  CollectionKey extends keyof Collection,
  Accumulator
> (
  collection: Collection,
  iteratee: (accum: Accumulator, item: CollectionItem, index: CollectionKey) => Accumulator,
  accumulator: Accumulator
): Accumulator {
  if (_.isObject(accumulator)) {
    throw new Error('Only simple primitives (boolean, numbers, strings...) are allowed')
  }

  if (_.isArray(collection)) {
    return reduceArrayCollection()
  }

  if (_.isObject(collection)) {
    return reduceObjectCollection()
  }

  throw new Error('Currently only arrays and objects are supported')

  function reduceArrayCollection () {
    for (let index = 0; index < collection.length; index++) {
      const oldAccumulator = accumulator
      accumulator = iteratee(accumulator, collection[index], index as CollectionKey)

      if (accumulator !== oldAccumulator) return accumulator
    }

    return accumulator
  }

  function reduceObjectCollection () {
    for (const key in collection) {
      if (!collection.hasOwnProperty(key)) continue

      const oldAccumulator = accumulator
      accumulator = iteratee(accumulator, collection[key], key as any as CollectionKey)

      if (accumulator !== oldAccumulator) return accumulator
    }

    return accumulator
  }
}

declare module 'lodash' {
  interface LoDashStatic {
    /**
     * Reduce which finishes as soon as accumulator changes.
     *
     * @param collection Collection to be reduced.
     * @param iteratee Function that will be called in each iteration.
     * It'll receive as parameters the accumulator, the value and the index.
     * @param accumulator Note that is **must** be a simple primitive.
     * @returns Accumulated value.
     */
    shortcuttedReduce<T, TResult>(
      collection: T[] | null | undefined,
      callback: MemoListIterator<T, TResult, T[]>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T, TResult>(
      collection: List<T> | null | undefined,
      callback: MemoListIterator<T, TResult, List<T>>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object, TResult>(
      collection: T | null | undefined,
      callback: MemoObjectIterator<T[keyof T], TResult, T>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      collection: T[] | null | undefined,
      callback: MemoListIterator<T, T, T[]>
    ): T | undefined;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      collection: List<T> | null | undefined,
      callback: MemoListIterator<T, T, List<T>>
    ): T | undefined;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object>(
      collection: T | null | undefined,
      callback: MemoObjectIterator<T[keyof T], T[keyof T], T>
    ): T[keyof T] | undefined;
  }

  interface LoDashImplicitWrapper<TValue> {
    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T, TResult>(
      this: LoDashImplicitWrapper<T[] | null | undefined>,
      callback: MemoListIterator<T, TResult, T[]>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T, TResult>(
      this: LoDashImplicitWrapper<List<T> | null | undefined>,
      callback: MemoListIterator<T, TResult, List<T>>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object, TResult>(
      this: LoDashImplicitWrapper<T | null | undefined>,
      callback: MemoObjectIterator<T[keyof T], TResult, T>,
      accumulator: TResult
    ): TResult;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      this: LoDashImplicitWrapper<T[] | null | undefined>,
      callback: MemoListIterator<T, T, T[]>
    ): T | undefined;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      this: LoDashImplicitWrapper<List<T> | null | undefined>,
      callback: MemoListIterator<T, T, List<T>>
    ): T | undefined;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object>(
      this: LoDashImplicitWrapper<T | null | undefined>,
      callback: MemoObjectIterator<T[keyof T], T[keyof T], T>
    ): T[keyof T] | undefined;
  }

  interface LoDashExplicitWrapper<TValue> {
    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T, TResult>(
      this: LoDashExplicitWrapper<T[] | null | undefined>,
      callback: MemoListIterator<T, TResult, T[]>,
      accumulator: TResult
    ): LoDashExplicitWrapper<TResult>;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T, TResult>(
      this: LoDashExplicitWrapper<List<T> | null | undefined>,
      callback: MemoListIterator<T, TResult, List<T>>,
      accumulator: TResult
    ): LoDashExplicitWrapper<TResult>;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object, TResult>(
      this: LoDashExplicitWrapper<T | null | undefined>,
      callback: MemoObjectIterator<T[keyof T], TResult, T>,
      accumulator: TResult
    ): LoDashExplicitWrapper<TResult>;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      this: LoDashExplicitWrapper<T[] | null | undefined>,
      callback: MemoListIterator<T, T, T[]>
    ): LoDashExplicitWrapper<T | undefined>;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T>(
      this: LoDashExplicitWrapper<List<T> | null | undefined>,
      callback: MemoListIterator<T, T, List<T>>
    ): LoDashExplicitWrapper<T | undefined>;

    /**
     * @see _.shortcuttedReduce
     */
    shortcuttedReduce<T extends object>(
      this: LoDashExplicitWrapper<T | null | undefined>,
      callback: MemoObjectIterator<T[keyof T], T[keyof T], T>
    ): LoDashExplicitWrapper<T[keyof T] | undefined>;
  }
}