From 2f55e19e483c9f7f86429d7945be45f000d443cb Mon Sep 17 00:00:00 2001 From: Yeom-JinHo Date: Mon, 24 Nov 2025 20:00:14 +0900 Subject: [PATCH 1/3] feat(uniqBy): support property-key selector in uniqBy --- src/array/uniqBy.ts | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/array/uniqBy.ts b/src/array/uniqBy.ts index 6c7cedd64..97c555df5 100644 --- a/src/array/uniqBy.ts +++ b/src/array/uniqBy.ts @@ -1,37 +1,45 @@ /** * Returns a new array containing only the unique elements from the original array, - * based on the values returned by the mapper function. + * based on either: + * + * - the value of a given property key, or + * - the value returned by the selector function. * * When duplicates are found, the first occurrence is kept and the rest are discarded. * * @template T - The type of elements in the array. - * @template U - The type of mapped elements. - * @param {T[]} arr - The array to process. - * @param {(item: T) => U} mapper - The function used to convert the array elements. - * @returns {T[]} A new array containing only the unique elements from the original array, based on the values returned by the mapper function. + * @template U - The type of the value returned by the selector. + * @param {readonly T[]} arr - The array to process. + * @param {((item: T) => U) | keyof T} selector - Either: + * - a function that selects the value used to determine uniqueness, or + * - a property key of `T` used to determine uniqueness. + * @returns {T[]} A new array containing only the unique elements from the original array. * * @example - * ```ts + * // Using a selector function * uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor); * // [1.2, 2.1, 3.2, 5.7, 7.19] - * ``` - * * @example + * // Using a property key * const array = [ * { category: 'fruit', name: 'apple' }, * { category: 'fruit', name: 'banana' }, * { category: 'vegetable', name: 'carrot' }, * ]; - * uniqBy(array, item => item.category).length - * // 2 - * ``` + * + * uniqBy(array, 'category'); + * // [ + * // { category: 'fruit', name: 'apple' }, + * // { category: 'vegetable', name: 'carrot' }, + * // ] */ -export function uniqBy(arr: readonly T[], mapper: (item: T) => U): T[] { - const map = new Map(); +export function uniqBy(arr: readonly T[], selector: ((item: T) => U) | keyof T): T[] { + const map = new Map(); for (let i = 0; i < arr.length; i++) { const item = arr[i]; - const key = mapper(item); + + const key = typeof selector === 'function' ? selector(item) : item[selector]; if (!map.has(key)) { map.set(key, item); From 7fc21aa03168e323b1e75e50a9cd0a543c910a96 Mon Sep 17 00:00:00 2001 From: Yeom-JinHo Date: Mon, 24 Nov 2025 20:02:29 +0900 Subject: [PATCH 2/3] test(uniqBy): add property-key cases for uniqBy --- src/array/uniqBy.spec.ts | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/array/uniqBy.spec.ts b/src/array/uniqBy.spec.ts index 41c3ec77e..d3bde8d44 100644 --- a/src/array/uniqBy.spec.ts +++ b/src/array/uniqBy.spec.ts @@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest'; import { uniqBy } from './uniqBy'; describe('uniqBy', () => { - it('should work with a `mapper`', () => { + it('should work with a selector function (mapper)', () => { expect(uniqBy([2.1, 1.2, 2.3], Math.floor)).toEqual([2.1, 1.2]); expect(uniqBy([1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19], Math.floor)).toEqual([1.2, 2.1, 3.2, 5.7, 7.19]); }); - it('should keep the first occurrence when duplicates are found', () => { + it('should keep the first occurrence when duplicates are found (selector function)', () => { const items = [ { id: 1, value: 'apple' }, { id: 2, value: 'banana' }, @@ -24,4 +24,42 @@ describe('uniqBy', () => { { id: 3, value: 'cherry' }, ]); }); + it('should work with a property key selector', () => { + const items = [ + { id: 1, value: 'jinho' }, + { id: 2, value: 'dohee' }, + { id: 1, value: 'jihun' }, + { id: 3, value: 'taeho' }, + { id: 2, value: 'eunji' }, + ]; + + const result = uniqBy(items, 'id'); + + expect(result).toEqual([ + { id: 1, value: 'jinho' }, + { id: 2, value: 'dohee' }, + { id: 3, value: 'taeho' }, + ]); + }); + + it('should work with a property key selector for string fields', () => { + const items = [ + { category: 'fruit', name: 'apple' }, + { category: 'fruit', name: 'banana' }, + { category: 'vegetable', name: 'carrot' }, + { category: 'vegetable', name: 'onion' }, + ]; + + const result = uniqBy(items, 'category'); + + expect(result).toEqual([ + { category: 'fruit', name: 'apple' }, + { category: 'vegetable', name: 'carrot' }, + ]); + }); + + it('should return an empty array when input is empty', () => { + expect(uniqBy([], Math.floor)).toEqual([]); + expect(uniqBy([] as Array<{ id: number }>, 'id')).toEqual([]); + }); }); From 8fb77de3bff542067f90af0d3d950cb69ec5bbf2 Mon Sep 17 00:00:00 2001 From: Yeom-JinHo Date: Mon, 24 Nov 2025 20:15:16 +0900 Subject: [PATCH 3/3] docs(uniqBy): update uniqBy docs for selector key support --- docs/ja/reference/array/uniqBy.md | 36 +++++++++++++++++------- docs/ko/reference/array/uniqBy.md | 36 +++++++++++++++++------- docs/reference/array/uniqBy.md | 39 ++++++++++++++++++++------ docs/zh_hans/reference/array/uniqBy.md | 38 +++++++++++++++++-------- 4 files changed, 110 insertions(+), 39 deletions(-) diff --git a/docs/ja/reference/array/uniqBy.md b/docs/ja/reference/array/uniqBy.md index a0bb51919..c583ab5fd 100644 --- a/docs/ja/reference/array/uniqBy.md +++ b/docs/ja/reference/array/uniqBy.md @@ -1,16 +1,19 @@ # uniqBy -変換関数が返す値を基準に、配列から重複した要素を除いた新しい配列を返します。 +セレクター関数または特定のプロパティキーを基準に重複要素を除いた新しい配列を返します。 ```typescript -const uniqueArray = uniqBy(arr, mapper); +const uniqueArray = uniqBy(arr, selector); ``` ## 使用法 -### `uniqBy(arr, mapper)` +### `uniqBy(arr, selector)` -配列の要素を特定の基準で変換して重複を判断したい場合は `uniqBy` を使用してください。変換関数が同じ値を返す要素のうち最初に現れるものだけを残します。 +特定の基準で重複を判定したいときに `uniqBy` を使用してください。セレクターが同じ値を返す要素のうち最初に出現したものだけを残します。セレクターは次のいずれかです。 + +- 各要素を比較値に変換する関数 +- 要素のプロパティキー(例: `'id'`, `'age'`) ```typescript import { uniqBy } from 'es-toolkit/array'; @@ -20,7 +23,7 @@ const numbers = [1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19]; const result = uniqBy(numbers, Math.floor); console.log(result); // [1.2, 2.1, 3.2, 5.7, 7.19] -// オブジェクト配列で特定のプロパティを基準に重複を除きます。 +// セレクター関数で特定のプロパティを基準にします。 const users = [ { id: 1, name: 'john', age: 30 }, { id: 2, name: 'jane', age: 30 }, @@ -31,13 +34,18 @@ const uniqueByAge = uniqBy(users, user => user.age); console.log(uniqueByAge); // [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] +// 同じ処理をプロパティキーだけで書くこともできます。 +const uniqueByAgeKey = uniqBy(users, 'age'); +console.log(uniqueByAgeKey); +// [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] + // 文字列の長さを基準に重複を除きます。 const words = ['apple', 'pie', 'banana', 'cat', 'dog']; const uniqueByLength = uniqBy(words, word => word.length); console.log(uniqueByLength); // ['apple', 'pie', 'banana'] ``` -複雑なオブジェクトでも特定のフィールドの組み合わせを基準にできます。 +複雑なオブジェクトでも複数フィールドの組み合わせを基準にできます。 ```typescript import { uniqBy } from 'es-toolkit/array'; @@ -49,18 +57,26 @@ const products = [ { category: 'fruit', name: 'grape' }, ]; -// カテゴリを基準に重複を除きます。 +// カテゴリで重複を除外(セレクター関数) const uniqueByCategory = uniqBy(products, item => item.category); console.log(uniqueByCategory.length); // 2 console.log(uniqueByCategory); // [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] + +// プロパティキーだけでも同じ結果になります。 +const uniqueByCategoryKey = uniqBy(products, 'category'); +console.log(uniqueByCategoryKey.length); // 2 +console.log(uniqueByCategoryKey); +// [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] ``` #### パラメータ -- `arr` (`readonly T[]`): 重複を除く配列です。 -- `mapper` (`(item: T) => U`): 各要素を比較する値に変換する関数です。 +- `arr` (`readonly T[]`): 重複を除外する対象の配列です。 +- `selector` (`((item: T) => U) | keyof T`): + - 各要素を比較値へ変換する関数、または + - 重複判定に使うプロパティキーです。 #### 戻り値 -(`T[]`): 変換関数の結果を基準に重複が除かれた新しい配列です。元の配列で最初に現れる順序を保持します。 +(`T[]`): セレクターの結果を基準に重複を除いた新しい配列です。元の配列で最初に現れる順序を保持します。 diff --git a/docs/ko/reference/array/uniqBy.md b/docs/ko/reference/array/uniqBy.md index 72c3483b3..b9b128d98 100644 --- a/docs/ko/reference/array/uniqBy.md +++ b/docs/ko/reference/array/uniqBy.md @@ -1,26 +1,29 @@ # uniqBy -변환 함수가 반환하는 값을 기준으로 배열에서 중복된 요소들을 제거한 새로운 배열을 반환해요. +셀렉터 함수나 특정 프로퍼티 키를 기준으로 중복 요소를 제거한 새로운 배열을 반환해요. ```typescript -const uniqueArray = uniqBy(arr, mapper); +const uniqueArray = uniqBy(arr, selector); ``` ## 사용법 -### `uniqBy(arr, mapper)` +### `uniqBy(arr, selector)` -배열의 요소들을 특정 기준으로 변환해서 중복을 판단하고 싶을 때 `uniqBy`를 사용하세요. 변환 함수가 같은 값을 반환하는 요소들 중 처음 나타나는 것만 남겨요. +특정 기준으로 중복을 판단하고 싶을 때 `uniqBy`를 사용하세요. 셀렉터가 같은 값을 반환하는 요소 중 처음 등장한 것만 남겨요. 셀렉터는 다음 둘 중 하나예요. + +- 각 요소를 비교 값으로 변환하는 함수 +- 요소의 프로퍼티 키(예: `'id'`, `'age'`) ```typescript import { uniqBy } from 'es-toolkit/array'; -// 소수점 숫자들을 내림차순으로 변환해서 중복 제거해요. +// 소수점 숫자들을 내림해서 중복 제거해요. const numbers = [1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19]; const result = uniqBy(numbers, Math.floor); console.log(result); // [1.2, 2.1, 3.2, 5.7, 7.19] -// 객체 배열에서 특정 속성을 기준으로 중복 제거해요. +// 셀렉터 함수로 특정 프로퍼티를 기준 삼아요. const users = [ { id: 1, name: 'john', age: 30 }, { id: 2, name: 'jane', age: 30 }, @@ -31,13 +34,18 @@ const uniqueByAge = uniqBy(users, user => user.age); console.log(uniqueByAge); // [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] +// 같은 로직을 프로퍼티 키로도 표현할 수 있어요. +const uniqueByAgeKey = uniqBy(users, 'age'); +console.log(uniqueByAgeKey); +// [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] + // 문자열 길이를 기준으로 중복 제거해요. const words = ['apple', 'pie', 'banana', 'cat', 'dog']; const uniqueByLength = uniqBy(words, word => word.length); console.log(uniqueByLength); // ['apple', 'pie', 'banana'] ``` -복잡한 객체에서도 특정 필드의 조합을 기준으로 할 수 있어요. +복잡한 객체에서도 여러 필드 조합을 기준으로 사용할 수 있어요. ```typescript import { uniqBy } from 'es-toolkit/array'; @@ -49,18 +57,26 @@ const products = [ { category: 'fruit', name: 'grape' }, ]; -// 카테고리를 기준으로 중복 제거해요. +// 카테고리를 기준으로 중복 제거 (셀렉터 함수) const uniqueByCategory = uniqBy(products, item => item.category); console.log(uniqueByCategory.length); // 2 console.log(uniqueByCategory); // [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] + +// 프로퍼티 키만으로도 동일하게 사용할 수 있어요. +const uniqueByCategoryKey = uniqBy(products, 'category'); +console.log(uniqueByCategoryKey.length); // 2 +console.log(uniqueByCategoryKey); +// [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] ``` #### 파라미터 - `arr` (`readonly T[]`): 중복을 제거할 배열이에요. -- `mapper` (`(item: T) => U`): 각 요소를 비교할 값으로 변환하는 함수예요. +- `selector` (`((item: T) => U) | keyof T`): + - 요소를 비교 값으로 변환하는 함수, 또는 + - 중복을 판단할 때 사용할 프로퍼티 키예요. #### 반환 값 -(`T[]`): 변환 함수의 결과를 기준으로 중복이 제거된 새로운 배열이에요. 원본 배열에서 처음 나타나는 순서를 유지해요. +(`T[]`): 셀렉터 결과를 기준으로 중복이 제거된 새로운 배열이에요. 원본 배열에서 처음 나타나는 순서를 유지해요. diff --git a/docs/reference/array/uniqBy.md b/docs/reference/array/uniqBy.md index 550ab1001..241c239c2 100644 --- a/docs/reference/array/uniqBy.md +++ b/docs/reference/array/uniqBy.md @@ -1,16 +1,25 @@ # uniqBy -Returns a new array with duplicate elements removed based on values returned by the transformation function. +Returns a new array with duplicate elements removed based on either: + +- values returned by the selector function, or +- the value of a given property key. ```typescript -const uniqueArray = uniqBy(arr, mapper); +const uniqueArray = uniqBy(arr, selector); ``` ## Usage -### `uniqBy(arr, mapper)` +### `uniqBy(arr, selector)` + +Use `uniqBy` when you want to determine duplicates by a specific criterion. +It only keeps the first occurrence among elements for which the selector returns the same value. -Use `uniqBy` when you want to transform elements by a specific criterion and determine duplicates. It only keeps the first occurrence among elements for which the transformation function returns the same value. +The selector can be: + +- a function that transforms each element into a comparison value, or +- a property key of the elements (e.g. 'id', 'age'). ```typescript import { uniqBy } from 'es-toolkit/array'; @@ -20,7 +29,7 @@ const numbers = [1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19]; const result = uniqBy(numbers, Math.floor); console.log(result); // [1.2, 2.1, 3.2, 5.7, 7.19] -// Remove duplicates from an object array based on a specific property. +// Remove duplicates from an object array based on a specific property using a selector function. const users = [ { id: 1, name: 'john', age: 30 }, { id: 2, name: 'jane', age: 30 }, @@ -31,6 +40,11 @@ const uniqueByAge = uniqBy(users, user => user.age); console.log(uniqueByAge); // [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] +// The same but using a property key. +const uniqueByAgeKey = uniqBy(users, 'age'); +console.log(uniqueByAgeKey); +// [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] + // Remove duplicates based on string length. const words = ['apple', 'pie', 'banana', 'cat', 'dog']; const uniqueByLength = uniqBy(words, word => word.length); @@ -49,18 +63,27 @@ const products = [ { category: 'fruit', name: 'grape' }, ]; -// Remove duplicates based on category. +// Remove duplicates based on category using a selector function. const uniqueByCategory = uniqBy(products, item => item.category); console.log(uniqueByCategory.length); // 2 console.log(uniqueByCategory); // [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] + +// Or using a property key directly. +const uniqueByCategoryKey = uniqBy(products, 'category'); +console.log(uniqueByCategoryKey.length); // 2 +console.log(uniqueByCategoryKey); +// [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] ``` #### Parameters - `arr` (`readonly T[]`): The array from which to remove duplicates. -- `mapper` (`(item: T) => U`): A function that transforms each element into a value for comparison. +- `selector` (`((item: T) => U) | keyof T`): + - A function that selects a value for comparison, or + - a property key of `T` used to determine uniqueness. #### Returns -(`T[]`): A new array with duplicates removed based on the transformation function's results. Preserves the order in which they first appear in the original array. +(`T[]`): A new array with duplicates removed based on the selector’s result. +It preserves the order in which elements first appear in the original array. diff --git a/docs/zh_hans/reference/array/uniqBy.md b/docs/zh_hans/reference/array/uniqBy.md index c2c8bec0a..80b3a0dfc 100644 --- a/docs/zh_hans/reference/array/uniqBy.md +++ b/docs/zh_hans/reference/array/uniqBy.md @@ -1,26 +1,29 @@ # uniqBy -根据转换函数返回的值,返回去除数组中重复元素后的新数组。 +根据选择器函数或指定属性键返回的值,返回去除数组中重复元素后的新数组。 ```typescript -const uniqueArray = uniqBy(arr, mapper); +const uniqueArray = uniqBy(arr, selector); ``` ## 用法 -### `uniqBy(arr, mapper)` +### `uniqBy(arr, selector)` -当您想根据特定标准转换数组元素来判断重复时,请使用 `uniqBy`。对于转换函数返回相同值的元素,只保留首次出现的元素。 +当您想根据特定标准判断重复时,请使用 `uniqBy`。对于选择器得出相同值的元素,只保留首次出现的元素。选择器可以是: + +- 将元素转换为比较值的函数 +- 元素的属性键(如 `'id'`、`'age'`) ```typescript import { uniqBy } from 'es-toolkit/array'; -// 将小数向下取整转换后去除重复项。 +// 将小数向下取整后去除重复项。 const numbers = [1.2, 1.5, 2.1, 3.2, 5.7, 5.3, 7.19]; const result = uniqBy(numbers, Math.floor); console.log(result); // [1.2, 2.1, 3.2, 5.7, 7.19] -// 根据对象数组的特定属性去除重复项。 +// 使用选择器函数按特定属性去重。 const users = [ { id: 1, name: 'john', age: 30 }, { id: 2, name: 'jane', age: 30 }, @@ -31,13 +34,18 @@ const uniqueByAge = uniqBy(users, user => user.age); console.log(uniqueByAge); // [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] -// 根据字符串长度去除重复项。 +// 也可以直接传入属性键。 +const uniqueByAgeKey = uniqBy(users, 'age'); +console.log(uniqueByAgeKey); +// [{ id: 1, name: 'john', age: 30 }, { id: 3, name: 'joe', age: 25 }] + +// 根据字符串长度去重。 const words = ['apple', 'pie', 'banana', 'cat', 'dog']; const uniqueByLength = uniqBy(words, word => word.length); console.log(uniqueByLength); // ['apple', 'pie', 'banana'] ``` -对于复杂对象也可以根据特定字段的组合进行去重。 +对于复杂对象,也可以根据多个字段的组合来判断重复。 ```typescript import { uniqBy } from 'es-toolkit/array'; @@ -49,18 +57,26 @@ const products = [ { category: 'fruit', name: 'grape' }, ]; -// 根据类别去除重复项。 +// 使用选择器函数按类别去重 const uniqueByCategory = uniqBy(products, item => item.category); console.log(uniqueByCategory.length); // 2 console.log(uniqueByCategory); // [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] + +// 仅用属性键也能实现同样的结果 +const uniqueByCategoryKey = uniqBy(products, 'category'); +console.log(uniqueByCategoryKey.length); // 2 +console.log(uniqueByCategoryKey); +// [{ category: 'fruit', name: 'apple' }, { category: 'vegetable', name: 'carrot' }] ``` #### 参数 - `arr` (`readonly T[]`): 要去除重复项的数组。 -- `mapper` (`(item: T) => U`): 将每个元素转换为用于比较的值的函数。 +- `selector` (`((item: T) => U) | keyof T`): + - 将元素转换为比较值的函数,或 + - 用于判断唯一性的属性键。 #### 返回值 -(`T[]`): 根据转换函数的结果去除重复项后的新数组。保持原数组中首次出现的顺序。 +(`T[]`): 根据选择器结果去除重复项后的新数组。保持原数组中首次出现的顺序。