diff --git a/docs/ja/reference/compat/array/reduce.md b/docs/ja/reference/compat/array/reduce.md new file mode 100644 index 000000000..36454202e --- /dev/null +++ b/docs/ja/reference/compat/array/reduce.md @@ -0,0 +1,74 @@ +# reduce + +::: info +この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。 + +`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。 +::: + +リデューサー関数を使用して、配列またはオブジェクトを単一の値に減らします。 + +配列の要素またはオブジェクトの値を1つずつ繰り返し、「リデューサー」と呼ばれる特別な関数を適用します。 +前のステップの結果と現在の要素を使用して計算を実行します。 +すべての要素を繰り返した後、最終結果を返します。 + +`reduce()` 関数が開始されるとき、使用する前のステップの結果はありません。 +初期値を提供すると、その値から開始します。 +初期値を提供しない場合、配列の最初の要素またはオブジェクトの最初の値を使用し、2番目の要素または値から計算を開始します。 + +## インターフェース + +```typescript +function reduce( + collection: T[], + iteratee: (accumulator: U, value: T, index: number, collection: T[]) => U, + initialValue: U +): U; +function reduce(collection: T[], iteratee: (accumulator: T, value: T, index: number, collection: T[]) => T): T; + +function reduce( + collection: ArrayLike, + iteratee: (accumulator: U, value: T, index: number, collection: ArrayLike) => U, + initialValue: U +): U; +function reduce( + collection: ArrayLike, + iteratee: (accumulator: T, value: T, index: number, collection: ArrayLike) => T +): T; + +function reduce( + collection: T, + iteratee: (accumulator: U, value: T[keyof T], key: keyof T, collection: T) => U, + initialValue: U +): U; +function reduce( + collection: T, + iteratee: (accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => T[keyof T] +): T[keyof T]; +``` + +### パラメータ + +- `collection` (`T[] | ArrayLike | Record`): 反復処理を行うコレクション。 +- `iteratee` (`(accumulator: U, value: T, index, collection) => any)`): 反復ごとに呼び出される関数。 +- `initialValue` (`U`): 初期値。 + +### 戻り値 + +(`any`): 蓄積された値。 + +## 例 + +```typescript +// Using a reducer function +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value, 0); // => 6 + +// Using a reducer function with initialValue +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value % 2 === 0, true); // => false + +// Using an object as the collection +const obj = { a: 1, b: 2, c: 3 }; +reduce(obj, (acc, value) => acc + value, 0); // => 6 +``` diff --git a/docs/ko/reference/compat/array/reduce.md b/docs/ko/reference/compat/array/reduce.md new file mode 100644 index 000000000..896b0638b --- /dev/null +++ b/docs/ko/reference/compat/array/reduce.md @@ -0,0 +1,74 @@ +# reduce + +::: info +이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요. + +`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요. +::: + +리듀서 함수를 사용해서 배열 또는 객체를 하나의 값으로 줄여요. + +배열의 요소 또는 객체의 값을 하나씩 순회하면서, "리듀서"라고 불리는 특별한 함수를 적용해요. +이전 단계의 결과와 현재 요소를 사용해서 계산을 수행해요. +모든 요소를 순회한 다음 최종 결과를 반환해요. + +`reduce()` 함수가 시작될 때 사용할 이전 단계의 결과가 없어요. +초기 값을 제공하면 그 값으로 시작해요. +초기 값을 제공하지 않으면 배열의 첫 번째 요소나 객체의 첫 번째 값을 사용하고, 두 번째 요소나 값부터 계산을 시작해요. + +## 인터페이스 + +```typescript +function reduce( + collection: T[], + iteratee: (accumulator: U, value: T, index: number, collection: T[]) => U, + initialValue: U +): U; +function reduce(collection: T[], iteratee: (accumulator: T, value: T, index: number, collection: T[]) => T): T; + +function reduce( + collection: ArrayLike, + iteratee: (accumulator: U, value: T, index: number, collection: ArrayLike) => U, + initialValue: U +): U; +function reduce( + collection: ArrayLike, + iteratee: (accumulator: T, value: T, index: number, collection: ArrayLike) => T +): T; + +function reduce( + collection: T, + iteratee: (accumulator: U, value: T[keyof T], key: keyof T, collection: T) => U, + initialValue: U +): U; +function reduce( + collection: T, + iteratee: (accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => T[keyof T] +): T[keyof T]; +``` + +### 파라미터 + +- `collection` (`T[] | ArrayLike | Record | null | undefined`): 반복할 컬렉션. +- `iteratee` (`((accumulator: any, value: any, index: PropertyKey, collection: any) => any) | PropertyKey | object`): 반복할 때 호출되는 함수. +- `initialValue` (`any`): 초기 값. + +### 반환 값 + +(`any`): 하나의 값으로 줄여진 값. + +## 예시 + +```typescript +// Using a reducer function +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value, 0); // => 6 + +// Using a reducer function with initialValue +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value % 2 === 0, true); // => false + +// Using an object as the collection +const obj = { a: 1, b: 2, c: 3 }; +reduce(obj, (acc, value) => acc + value, 0); // => 6 +``` diff --git a/docs/reference/compat/array/reduce.md b/docs/reference/compat/array/reduce.md new file mode 100644 index 000000000..fd0a58c30 --- /dev/null +++ b/docs/reference/compat/array/reduce.md @@ -0,0 +1,74 @@ +# reduce + +::: info +This function is only available in `es-toolkit/compat` for compatibility reasons. It either has alternative native JavaScript APIs or isn’t fully optimized yet. + +When imported from `es-toolkit/compat`, it behaves exactly like lodash and provides the same functionalities, as detailed [here](../../../compatibility.md). +::: + +Reduces an array or object to a single value using an iteratee function. + +The `reduce()` function goes through each element in an array or values of an object and applies a special function (called a "reducer") to them, one by one. +This function takes the result of the previous step and the current element to perform a calculation. +After going through all the elements, the function gives you one final result. + +When the `reduce()` function starts, there's no previous result to use. +If you provide an initial value, it starts with that. +If not, it uses the first element of the array or the first value of the object and begins with the second element or value for the calculation. + +## Signature + +```typescript +function reduce( + collection: T[], + iteratee: (accumulator: U, value: T, index: number, collection: T[]) => U, + initialValue: U +): U; +function reduce(collection: T[], iteratee: (accumulator: T, value: T, index: number, collection: T[]) => T): T; + +function reduce( + collection: ArrayLike, + iteratee: (accumulator: U, value: T, index: number, collection: ArrayLike) => U, + initialValue: U +): U; +function reduce( + collection: ArrayLike, + iteratee: (accumulator: T, value: T, index: number, collection: ArrayLike) => T +): T; + +function reduce( + collection: T, + iteratee: (accumulator: U, value: T[keyof T], key: keyof T, collection: T) => U, + initialValue: U +): U; +function reduce( + collection: T, + iteratee: (accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => T[keyof T] +): T[keyof T]; +``` + +### Parameters + +- `collection` (`T[] | ArrayLike | Record`): The collection to iterate over. +- `iteratee` (`(accumulator: U, value: T, index, collection) => any)`): The function invoked per iteration. +- `initialValue` (`U`): The initial value. + +### Returns + +(`any`): Returns the accumulated value. + +## Examples + +```typescript +// Using a reducer function +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value, 0); // => 6 + +// Using a reducer function with initialValue +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value % 2 === 0, true); // => false + +// Using an object as the collection +const obj = { a: 1, b: 2, c: 3 }; +reduce(obj, (acc, value) => acc + value, 0); // => 6 +``` diff --git a/docs/zh_hans/reference/compat/array/reduce.md b/docs/zh_hans/reference/compat/array/reduce.md new file mode 100644 index 000000000..3247ec30a --- /dev/null +++ b/docs/zh_hans/reference/compat/array/reduce.md @@ -0,0 +1,74 @@ +# reduce + +::: info +出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 + +从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 +::: + +使用迭代函数将数组或对象减少为单个值。 + +`reduce()` 函数遍历数组中的每个元素或对象的值,并逐个应用一个特殊函数(称为“reducer”)。 +此函数使用上一步的结果和当前元素来执行计算。 +遍历所有元素后,该函数会给出一个最终结果。 + +当 `reduce()` 函数开始时,没有可用的上一步结果。 +如果提供了初始值,则从该值开始。 +如果没有,则使用数组的第一个元素或对象的第一个值,并从第二个元素或值开始计算。 + +## 签名 + +```typescript +function reduce( + collection: T[], + iteratee: (accumulator: U, value: T, index: number, collection: T[]) => U, + initialValue: U +): U; +function reduce(collection: T[], iteratee: (accumulator: T, value: T, index: number, collection: T[]) => T): T; + +function reduce( + collection: ArrayLike, + iteratee: (accumulator: U, value: T, index: number, collection: ArrayLike) => U, + initialValue: U +): U; +function reduce( + collection: ArrayLike, + iteratee: (accumulator: T, value: T, index: number, collection: ArrayLike) => T +): T; + +function reduce( + collection: T, + iteratee: (accumulator: U, value: T[keyof T], key: keyof T, collection: T) => U, + initialValue: U +): U; +function reduce( + collection: T, + iteratee: (accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => T[keyof T] +): T[keyof T]; +``` + +### 参数 + +- `collection` (`T[] | ArrayLike | Record`): 要迭代的集合。 +- `iteratee` (`(accumulator: U, value: T, index, collection) => any)`): 每次迭代时调用的函数。 +- `initialValue` (`U`): 初始值。 + +### 返回值 + +(`any`): 返回累积值。 + +## 示例 + +```typescript +// Using a reducer function +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value, 0); // => 6 + +// Using a reducer function with initialValue +const array = [1, 2, 3]; +reduce(array, (acc, value) => acc + value % 2 === 0, true); // => false + +// Using an object as the collection +const obj = { a: 1, b: 2, c: 3 }; +reduce(obj, (acc, value) => acc + value, 0); // => 6 +``` diff --git a/src/compat/array/reduce.spec.ts b/src/compat/array/reduce.spec.ts new file mode 100644 index 000000000..16fc0b390 --- /dev/null +++ b/src/compat/array/reduce.spec.ts @@ -0,0 +1,180 @@ +import { describe, expect, it } from 'vitest'; +import * as lodashStable from 'es-toolkit/compat'; +import { head } from './head'; +import { reduce } from './reduce'; +import { empties } from '../_internal/empties'; +import { MAX_SAFE_INTEGER } from '../_internal/MAX_SAFE_INTEGER'; +import { keys } from '../object/keys'; + +describe('reduce', () => { + const array = [1, 2, 3]; + + it(`should reduce a collection to a single value`, () => { + const actual = reduce(['a', 'b', 'c'], (accumulator, value) => accumulator + value, ''); + + expect(actual).toBe('abc'); + }); + + it(`should support empty collections without an initial \`accumulator\` value`, () => { + const actual: any[] = []; + const expected = lodashStable.map(empties, lodashStable.noop); + + lodashStable.each(empties, value => { + try { + // eslint-disable-next-line + // @ts-ignore + actual.push(reduce(value, lodashStable.noop)); + } catch (e) { + // + } + }); + + expect(actual).toEqual(expected); + }); + + it(`should support empty collections with an initial \`accumulator\` value`, () => { + const expected = lodashStable.map(empties, lodashStable.constant('x')); + + const actual = lodashStable.map(empties, value => { + try { + // eslint-disable-next-line + // @ts-ignore + return reduce(value, lodashStable.noop, 'x'); + } catch (e) { + // + } + }); + + expect(actual).toEqual(expected); + }); + + it(`should handle an initial \`accumulator\` value of \`undefined\``, () => { + const actual = reduce([], lodashStable.noop, undefined); + expect(actual).toBe(undefined); + }); + + it(`should return \`undefined\` for empty collections when no \`accumulator\` is given (test in IE > 9 and modern browsers)`, () => { + const array: any[] = []; + const object = { 0: 1, length: 0 }; + + if ('__proto__' in array) { + array.__proto__ = object; + expect(reduce(array, lodashStable.noop)).toBe(undefined); + } + // eslint-disable-next-line + // @ts-ignore + expect(reduce(object, lodashStable.noop)).toBe(undefined); + }); + + it('should use the first element of a collection as the default `accumulator`', () => { + // eslint-disable-next-line + // @ts-ignore + expect(reduce(array)).toBe(1); + }); + + it('should provide correct `iteratee` arguments when iterating an array', () => { + let args: any; + + reduce( + array, + function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }, + // eslint-disable-next-line + // @ts-ignore + 0 + ); + + expect(args).toEqual([0, 1, 0, array]); + + args = undefined; + // eslint-disable-next-line + // @ts-ignore + reduce(array, function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }); + + expect(args).toEqual([1, 2, 1, array]); + }); + + it('should provide correct `iteratee` arguments when iterating an object', () => { + let args: any; + const object = { a: 1, b: 2 }; + const firstKey = head(keys(object)); + + let expected = firstKey === 'a' ? [0, 1, 'a', object] : [0, 2, 'b', object]; + + // eslint-disable-next-line + // @ts-ignore + reduce( + object, + function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }, + 0 + ); + + expect(args).toEqual(expected); + + args = undefined; + expected = firstKey === 'a' ? [1, 2, 'b', object] : [2, 1, 'a', object]; + + // eslint-disable-next-line + // @ts-ignore + reduce(object, function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }); + + expect(args).toEqual(expected); + }); + + it(`should use \`isArrayLike\` to determine whether a value is array-like`, () => { + const isIteratedAsObject = function (object: any) { + let result = false; + + reduce( + object, + () => { + result = true; + }, + 0 as any + ); + return result; + }; + + const values = [-1, '1', 1.1, Object(1), MAX_SAFE_INTEGER + 1]; + const expected = lodashStable.map(values, lodashStable.stubTrue); + + const actual = lodashStable.map(values, length => isIteratedAsObject({ length: length })); + + // eslint-disable-next-line + const Foo = function (a: any) {}; + Foo.a = 1; + + expect(actual).toEqual(expected); + expect(isIteratedAsObject(Foo)).toBeTruthy(); + expect(isIteratedAsObject({ length: 0 })).toBeFalsy(); + }); + + it(`should ignore added \`object\` properties`, () => { + let count = 0; + const object: any = { a: 1 }; + + reduce( + object, + () => { + if (++count === 1) { + object.b = 2; + } + return true; + }, + object + ); + + expect(count).toBe(1); + }); +}); diff --git a/src/compat/array/reduce.ts b/src/compat/array/reduce.ts new file mode 100644 index 000000000..660f164e9 --- /dev/null +++ b/src/compat/array/reduce.ts @@ -0,0 +1,198 @@ +import { identity } from '../../function/identity.ts'; +import { range } from '../../math/range.ts'; +import { isArrayLike } from '../predicate/isArrayLike.ts'; + +/** + * Reduces an array to a single value using an iteratee function. + * + * The `reduce()` function goes through each element in an array and applies a special function (called a "reducer") to them, one by one. + * This function takes the result of the previous step and the current element to perform a calculation. + * After going through all the elements, the function gives you one final result. + * + * When the `reduce()` function starts, there's no previous result to use. + * If you provide an initial value, it starts with that. + * If not, it uses the first element of the array and begins with the second element for the calculation. + * + * @param {readonly T[]} collection - The collection to iterate over. + * @param {(accumulator: U, value: T, index: number, collection: readonly T[]) => U} iteratee - The function invoked per iteration. + * @param {U} initialValue - The initial value. + * @returns {U} - Returns the accumulated value. + * + * @example + * const arrayLike = [1, 2, 3]; + * reduce(arrayLike, (acc, value) => acc && value % 2 === 0, true); // => false + */ +export function reduce( + collection: readonly T[], + iteratee: (accumulator: U, value: T, index: number, collection: readonly T[]) => U, + initialValue: U +): U; + +/** + * Reduces an array to a single value using an iteratee function. + * + * The `reduce()` function goes through each element in an array and applies a special function (called a "reducer") to them, one by one. + * This function takes the result of the previous step and the current element to perform a calculation. + * After going through all the elements, the function gives you one final result. + * + * When the `reduce()` function starts, there's no previous result to use. + * If you provide an initial value, it starts with that. + * If not, it uses the first element of the array and begins with the second element for the calculation. + * + * @param {readonly T[]} collection - The collection to iterate over. + * @param {(accumulator: T, value: T, index: number, collection: readonly T[]) => T} iteratee - The function invoked per iteration. + * @returns {T} - Returns the accumulated value. + * + * @example + * const arrayLike = [1, 2, 3]; + * reduce(arrayLike, (acc, value) => acc + value); // => 6 + */ +export function reduce( + collection: readonly T[], + iteratee: (accumulator: T, value: T, index: number, collection: readonly T[]) => T +): T; + +/** + * Reduces an array to a single value using an iteratee function. + * + * The `reduce()` function goes through each element in an array and applies a special function (called a "reducer") to them, one by one. + * This function takes the result of the previous step and the current element to perform a calculation. + * After going through all the elements, the function gives you one final result. + * + * When the `reduce()` function starts, there's no previous result to use. + * If you provide an initial value, it starts with that. + * If not, it uses the first element of the array and begins with the second element for the calculation. + * + * @param {ArrayLike} collection - The collection to iterate over. + * @param {(accumulator: U, value: T, index: number, collection: ArrayLike) => U} iteratee - The function invoked per iteration. + * @param {U} initialValue - The initial value. + * @returns {U} - Returns the accumulated value. + * + * @example + * const arrayLike = {0: 1, 1: 2, 2: 3, length: 3}; + * reduce(arrayLike, (acc, value) => acc + value % 2 === 0, true); // => false + */ +export function reduce( + collection: ArrayLike, + iteratee: (accumulator: U, value: T, index: number, collection: ArrayLike) => U, + initialValue: U +): U; + +/** + * Reduces an array to a single value using an iteratee function. + * + * The `reduce()` function goes through each element in an array and applies a special function (called a "reducer") to them, one by one. + * This function takes the result of the previous step and the current element to perform a calculation. + * After going through all the elements, the function gives you one final result. + * + * When the `reduce()` function starts, there's no previous result to use. + * If you provide an initial value, it starts with that. + * If not, it uses the first element of the array and begins with the second element for the calculation. + * + * @param {ArrayLike} collection - The collection to iterate over. + * @param {(accumulator: U, value: T, index: number, collection: ArrayLike) => U} iteratee - The function invoked per iteration. + * @returns {T} - Returns the accumulated value. + * + * @example + * const arrayLike = {0: 1, 1: 2, 2: 3, length: 3}; + * reduce(arrayLike, (acc, value) => acc + value); // => 6 + */ +export function reduce( + collection: ArrayLike, + iteratee: (accumulator: T, value: T, index: number, collection: ArrayLike) => T +): T; + +/** + * Reduces an object to a single value using an iteratee function. + * + * @param {T} collection - The object to iterate over. + * @param {(accumulator: U, value: T[keyof T], key: string, collection: T) => U} iteratee - The function invoked per iteration. + * @param {U} initialValue - The initial value. + * @returns {U} - Returns the accumulated value. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * reduce(obj, (acc, value) => acc + value % 2 === 0, true); // => false + */ +export function reduce( + collection: T, + iteratee: (accumulator: U, value: T[keyof T], key: keyof T, collection: T) => U, + initialValue: U +): U; + +/** + * Reduces an object to a single value using an iteratee function. + * + * @param {T} collection - The object to iterate over. + * @param {(accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => U} iteratee - The function invoked per iteration. + * @returns {T[keyof T]} - Returns the accumulated value. + * + * @example + * const obj = { a: 1, b: 2, c: 3 }; + * reduce(obj, (acc, value) => acc + value); // => 6 + */ +export function reduce( + collection: T, + iteratee: (accumulator: T[keyof T], value: T[keyof T], key: keyof T, collection: T) => T[keyof T] +): T[keyof T]; + +/** + * Reduces a collection to a single value using an iteratee function. + * + * @param {T[] | ArrayLike | Record | null | undefined} collection - The collection to iterate over. + * @param {((accumulator: any, value: any, index: PropertyKey, collection: any) => any) | PropertyKey | object} iteratee - The function invoked per iteration or the key to reduce over. + * @param {any} initialValue - The initial value. + * @returns {any} - Returns the accumulated value. + * + * @example + * // Using a reducer function + * const array = [1, 2, 3]; + * reduce(array, (acc, value) => acc + value, 0); // => 6 + * + * @example + * // Using a reducer function with initialValue + * const array = [1, 2, 3]; + * reduce(array, (acc, value) => acc + value % 2 === 0, true); // => false + * + * @example + * // Using an object as the collection + * const obj = { a: 1, b: 2, c: 3 }; + * reduce(obj, (acc, value) => acc + value, 0); // => 6 + */ +export function reduce( + collection: ArrayLike | Record | null | undefined, + iteratee: (accumulator: any, value: any, index: any, collection: any) => any = identity, + accumulator?: any +): any { + if (!collection) { + return accumulator; + } + + let keys: any[]; + let startIndex = 0; + + if (isArrayLike(collection)) { + keys = range(0, collection.length); + + if (accumulator == null && collection.length > 0) { + accumulator = collection[0]; + startIndex += 1; + } + } else { + keys = Object.keys(collection); + + if (accumulator == null) { + accumulator = (collection as any)[keys[0]]; + startIndex += 1; + } + } + + for (let i = startIndex; i < keys.length; i++) { + const key = keys[i]; + const value = (collection as any)[key]; + + accumulator = iteratee(accumulator, value, key, collection); + } + + return accumulator; +} diff --git a/src/compat/index.ts b/src/compat/index.ts index 6cee3fa37..7c7dc3538 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -63,6 +63,7 @@ export { orderBy } from './array/orderBy.ts'; export { pull } from './array/pull.ts'; export { pullAll } from './array/pullAll.ts'; export { pullAllBy } from './array/pullAllBy.ts'; +export { reduce } from './array/reduce.ts'; export { remove } from './array/remove.ts'; export { reverse } from './array/reverse.ts'; export { sample } from './array/sample.ts';