From 9bc5fa98f406036a3bbec3030471b1f2c7de54b7 Mon Sep 17 00:00:00 2001 From: Sojin Park Date: Thu, 30 Jan 2025 22:31:22 +0900 Subject: [PATCH] feat(findLast): Implement findLast --- docs/ja/reference/compat/array/findLast.md | 155 ++++++++++++ docs/ko/reference/compat/array/findLast.md | 155 ++++++++++++ docs/reference/compat/array/findLast.md | 155 ++++++++++++ .../reference/compat/array/findLast.md | 155 ++++++++++++ src/compat/array/find.ts | 46 +--- src/compat/array/findLast.spec.ts | 158 ++++++++++++ src/compat/array/findLast.ts | 226 ++++++++++++++++++ src/compat/index.ts | 1 + 8 files changed, 1018 insertions(+), 33 deletions(-) create mode 100644 docs/ja/reference/compat/array/findLast.md create mode 100644 docs/ko/reference/compat/array/findLast.md create mode 100644 docs/reference/compat/array/findLast.md create mode 100644 docs/zh_hans/reference/compat/array/findLast.md create mode 100644 src/compat/array/findLast.spec.ts create mode 100644 src/compat/array/findLast.ts diff --git a/docs/ja/reference/compat/array/findLast.md b/docs/ja/reference/compat/array/findLast.md new file mode 100644 index 000000000..d6c884776 --- /dev/null +++ b/docs/ja/reference/compat/array/findLast.md @@ -0,0 +1,155 @@ +# findLast + +::: info +この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。 + +`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。 +::: + +配列やオブジェクトから条件に合う最後の値を見つけます。 + +条件は複数の方法で指定できます。 + +- **検査関数**: 各要素に対して検査する関数を実行します。最後に `true` を返す値が選択されます。 +- **部分オブジェクト**: 与えられたオブジェクトと部分的に一致する最後の要素が選択されます。 +- **プロパティ-値ペア**: 該当プロパティに対して値が一致する最後の要素が選択されます。 +- **プロパティ名**: 該当プロパティに対して真と評価される値を持つ最後の要素が選択されます。 + +## インターフェース + +```typescript +function findLast( + arr: T[], + doesMatch: (item: T, index: number, arr: T[]) => unknown, + fromIndex?: number +): T | undefined; +function findLast(arr: T[], doesMatch: Partial, fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: PropertyKey, fromIndex?: number): T | undefined; + +function findLast>( + object: T, + doesMatch: (item: T[keyof T], index: number, object: T) => unknown, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: Partial, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: [keyof T[keyof T], unknown], + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: PropertyKey, + fromIndex?: number +): T | undefined; +``` + +### パラメータ + +- `arr` (`T[]`) または `object` (`T`): 検索する配列またはオブジェクト。 + +::: info `arr` は `ArrayLike` であるか、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`find` 関数は `arr` を次のように処理します。 + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `arr` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + +::: info `object` は `null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`find` 関数は `object` を次のように処理します。 + +- `object` が `null` または `undefined` の場合、空のオブジェクトに変換されます。 + +::: + +- `doesMatch`: + + - 配列の場合: + + - **検査関数** (`(item: T, index: number, arr: T[]) => unknown`): 探している要素かどうかを返す関数。 + - **部分オブジェクト** (`Partial`): 一致させるプロパティと値を指定した部分オブジェクト。 + - **プロパティ-値ペア** (`[keyof T, unknown]`): 最初が一致させるプロパティ、2番目が一致させる値を表すタプル。 + - **プロパティ名** (`PropertyKey`): 真と評価される値を持っているか確認するプロパティ名。 + + - オブジェクトの場合: + - **検査関数** (`(item: T[keyof T], index: number, object: T) => unknown`): 探している要素かどうかを返す関数。 + - **部分値** (`Partial`): 一致させるプロパティと値を指定した部分オブジェクト。 + - **プロパティ-値ペア** (`[keyof T[keyof T], unknown]`): 最初が一致させるプロパティ、2番目が一致させる値を表すタプル。 + - **プロパティ名** (`PropertyKey`): 真と評価される値を持っているか確認するプロパティ名。 + +- `fromIndex` (`number`): 検索を開始するインデックス。デフォルトは `0`。 + +### 戻り値 + +(`T | undefined`): 与えられた条件を満たす最初の要素。ない場合は `undefined`。 + +## 例 + +### 配列の場合 + +```typescript +import { find } from 'es-toolkit/compat'; + +// 検査関数を使う場合 +const items = [1, 2, 3, 4, 5]; +const result = find(items, item => item > 3); +console.log(result); // 5 + +// 部分オブジェクトを使う場合 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// プロパティ-値ペアを使う場合 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// プロパティ名を使う場合 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` + +### オブジェクトの場合 + +```typescript +import { find } from 'es-toolkit/compat'; + +// 検査関数を使う場合 +const obj = { a: 1, b: 2, c: 3 }; +const result = find(obj, item => item > 2); +console.log(result); // 3 + +// 部分オブジェクトを使う場合 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = find(obj, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// プロパティ-値ペアを使う場合 +const items = { alice: { id: 1, name: 'Alice' }, bob: { id: 2, name: 'Bob' } }; +const result = find(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// プロパティ名を使う場合 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = find(obj, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` diff --git a/docs/ko/reference/compat/array/findLast.md b/docs/ko/reference/compat/array/findLast.md new file mode 100644 index 000000000..a289b0499 --- /dev/null +++ b/docs/ko/reference/compat/array/findLast.md @@ -0,0 +1,155 @@ +# findLast + +::: info +이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요. + +`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요. +::: + +배열이나 객체에서 조건에 맞는 마지막 값을 찾아요. + +조건은 여러 방법들로 명시할 수 있어요. + +- **검사 함수**: 각각의 요소에 대해서 검사하는 함수를 실행해요. 마지막으로 `true`를 반환하게 하는 값이 선택돼요. +- **부분 객체**: 주어진 객체와 부분적으로 일치하는 마지막 요소가 선택돼요. +- **프로퍼티-값 쌍**: 해당 프로퍼티에 대해서 값이 일치하는 마지막 요소가 선택돼요. +- **프로퍼티 이름**: 해당 프로퍼티에 대해서 참으로 평가되는 값을 가지는 마지막 요소가 선택돼요. + +## 인터페이스 + +```typescript +function findLast( + arr: T[], + doesMatch: (item: T, index: number, arr: T[]) => unknown, + fromIndex?: number +): T | undefined; +function findLast(arr: T[], doesMatch: Partial, fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: PropertyKey, fromIndex?: number): T | undefined; + +function findLast>( + object: T, + doesMatch: (item: T[keyof T], index: number, object: T) => unknown, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: Partial, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: [keyof T[keyof T], unknown], + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: PropertyKey, + fromIndex?: number +): T | undefined; +``` + +### 파라미터 + +- `arr` (`T[]`) or `object` (`T`): 검색할 배열이나 객체. + +::: info `arr`는 `ArrayLike`일 수도 있고, `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `find` 함수는 `arr`을 다음과 같이 처리해요: + +- `arr`가 `ArrayLike`인 경우 `Array.from(...)`을 사용하여 배열로 변환해요. +- `arr`가 `null` 또는 `undefined`인 경우 빈 배열로 간주돼요. + +::: + +::: info `object`는 `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `find` 함수는 `object`를 다음과 같이 처리해요: + +- `object`가 `null` 또는 `undefined`인 경우 빈 객체로 변환돼요. + +::: + +- `doesMatch`: + + - 배열의 경우: + + - **검사 함수** (`(item: T, index: number, arr: T[]) => unknown`): 찾는 요소인지 여부를 반환하는 함수. + - **부분 객체** (`Partial`): 일치시킬 프로퍼티와 값들을 명시한 부분 객체. + - **프로퍼티-값 쌍** (`[keyof T, unknown]`): 첫 번째가 일치시킬 프로퍼티, 두 번째가 일치시킬 값을 나타내는 튜플. + - **프로퍼티 이름** (`PropertyKey`): 참으로 평가되는 값을 가지고 있는지 확인할 프로퍼티 이름. + + - 객체의 경우: + - **검사 함수** (`(item: T[keyof T], index: number, object: T) => unknown`): 찾는 요소인지 여부를 반환하는 함수. + - **Partial value** (`Partial`): 일치시킬 프로퍼티와 값들을 명시한 부분 객체. + - **Property-value pair** (`[keyof T[keyof T], unknown]`): 첫 번째가 일치시킬 프로퍼티, 두 번째가 일치시킬 값을 나타내는 튜플. + - **Property name** (`PropertyKey`): 참으로 평가되는 값을 가지고 있는지 확인할 프로퍼티 이름. + +- `fromIndex` (`number`): 검색을 시작할 인덱스. 기본값은 `0`. + +### 반환 값 + +(`T | undefined`): 주어진 조건을 만족하는 첫 번째 요소. 없으면 `undefined`. + +## 예시 + +### 배열의 경우 + +```typescript +import { findLast } from 'es-toolkit/compat'; + +// 검사 함수를 쓰는 경우 +const items = [1, 2, 3, 4, 5]; +const result = findLast(items, item => item > 3); +console.log(result); // 5 + +// 부분 객체를 쓰는 경우 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// 프로퍼티-값 쌍을 쓰는 경우 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// 프로퍼티 이름을 쓰는 경우 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` + +### 객체의 경우 + +```typescript +import { findLast } from 'es-toolkit/compat'; + +// 검사 함수를 쓰는 경우 +const obj = { a: 1, b: 2, c: 3 }; +const result = findLast(obj, item => item > 2); +console.log(result); // 3 + +// 부분 객체를 쓰는 경우 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = findLast(obj, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// 프로퍼티-값 쌍을 쓰는 경우 +const items = { alice: { id: 1, name: 'Alice' }, bob: { id: 2, name: 'Bob' } }; +const result = findLast(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// 프로퍼티 이름을 쓰는 경우 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = findLast(obj, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` diff --git a/docs/reference/compat/array/findLast.md b/docs/reference/compat/array/findLast.md new file mode 100644 index 000000000..634eb09c3 --- /dev/null +++ b/docs/reference/compat/array/findLast.md @@ -0,0 +1,155 @@ +# findLast + +::: 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). +::: + +Finds the last item in an array or object that meets the specified condition. + +You can specify the condition in several ways: + +- **Predicate function**: If you provide a predicate function, the function will be applied to each item. The last item that makes the predicate function return `true` will be selected. +- **Partial object**: If you provide a partial object, the function will return the last item that matches the properties of the partial object. +- **Property-value pair**: If you provide a property-value pair, the function will return the last item that matches the property and value from the pair. +- **Property name**: If you provide a property name, the function will return the last item where the specified property has a truthy value. + +## Signature + +```typescript +function findLast( + arr: T[], + doesMatch: (item: T, index: number, arr: T[]) => unknown, + fromIndex?: number +): T | undefined; +function findLast(arr: T[], doesMatch: Partial, fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: PropertyKey, fromIndex?: number): T | undefined; + +function findLast>( + object: T, + doesMatch: (item: T[keyof T], index: number, object: T) => unknown, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: Partial, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: [keyof T[keyof T], unknown], + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: PropertyKey, + fromIndex?: number +): T | undefined; +``` + +### Parameters + +- `arr` (`T[]`) or `object` (`T`): The array or object to search through. + +::: info `arr` can be `ArrayLike`, `null`, or `undefined` + +To ensure full compatibility with lodash, the `find` function handles `arr` in this way: + +- If `arr` is an `ArrayLike`, it gets converted into an array using `Array.from(...)`. +- If `arr` is `null` or `undefined`, it will be treated as an empty array. + +::: + +::: info `object` can be `null` or `undefined` + +To ensure full compatibility with lodash, the `find` function handles `object` in this way: + +- If `object` is `null` or `undefined`, it will be converted into an empty object. + +::: + +- `doesMatch`: + + - For the first `find` overload with arrays: + + - **Predicate function** (`(item: T, index: number, arr: T[]) => unknown`): A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. + - **Partial object** (`Partial`): A partial object that specifies the properties to match. + - **Property-value pair** (`[keyof T, unknown]`): An array where the first element is the property key and the second element is the value to match. + - **Property name** (`PropertyKey`): The name of the property to check for a truthy value. + + - For the `find` overloads with objects: + - **Predicate function** (`(item: T[keyof T], index: number, object: T) => unknown`): A function that takes an item, its key, and the object, and returns a truthy value if the item matches the criteria. + - **Partial value** (`Partial`): A partial value to match against the values of the object. + - **Property-value pair** (`[keyof T[keyof T], unknown]`): An array where the first element is the property key and the second element is the value to match. + - **Property name** (`PropertyKey`): The name of the property to check for a truthy value. + +- `fromIndex` (`number`): The index to start the search from, defaults to `0`. + +### Returns + +(`T | undefined`): The last item that has the specified property value, or `undefined` if no match is found. + +## Examples + +### Arrays + +```typescript +import { findLast } from 'es-toolkit/compat'; + +// Using a predicate function +const items = [1, 2, 3, 4, 5]; +const result = findLast(items, item => item > 3); +console.log(result); // 5 + +// Using a partial object +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// Using a property-value pair +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// Using a property name +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = find(items, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` + +### Objects + +```typescript +import { find } from 'es-toolkit/compat'; + +// Using a predicate function +const obj = { a: 1, b: 2, c: 3 }; +const result = find(obj, item => item > 2); +console.log(result); // 3 + +// Using a partial value +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = find(obj, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// Using a property-value pair +const items = { alice: { id: 1, name: 'Alice' }, bob: { id: 2, name: 'Bob' } }; +const result = find(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// Using a property name +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = find(obj, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` diff --git a/docs/zh_hans/reference/compat/array/findLast.md b/docs/zh_hans/reference/compat/array/findLast.md new file mode 100644 index 000000000..c72bc4c30 --- /dev/null +++ b/docs/zh_hans/reference/compat/array/findLast.md @@ -0,0 +1,155 @@ +# findLast + +::: info +出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 + +从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 + +::: +查找数组或对象中最后一个符合指定条件的项。 + +您可以通过以下几种方式指定条件: + +- **谓词函数**:如果提供一个谓词函数,该函数将应用于每一项。返回 `true` 的最后一个项将被选中。 +- **部分对象**:如果提供一个部分对象,该函数将返回最后一个匹配部分对象属性的项。 +- **属性-值对**:如果提供一个属性-值对,该函数将返回最后一个匹配该属性和值的项。 +- **属性名称**:如果提供一个属性名称,该函数将返回最后一个指定属性具有真值的项。 + +## 签名 + +```typescript +function findLast( + arr: T[], + doesMatch: (item: T, index: number, arr: T[]) => unknown, + fromIndex?: number +): T | undefined; +function findLast(arr: T[], doesMatch: Partial, fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): T | undefined; +function findLast(arr: T[], doesMatch: PropertyKey, fromIndex?: number): T | undefined; + +function findLast>( + object: T, + doesMatch: (item: T[keyof T], index: number, object: T) => unknown, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: Partial, + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: [keyof T[keyof T], unknown], + fromIndex?: number +): T | undefined; +function findLast>( + object: T, + doesMatch: PropertyKey, + fromIndex?: number +): T | undefined; +``` + +### 参数 + +- `arr` (`T[]`) 或 `object` (`T`): 要搜索的数组或对象。 + +::: info `arr` 可以是 `ArrayLike`、`null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`find` 函数会按照以下方式处理 `arr`: + +- 如果 `arr` 是 `ArrayLike`,它将使用 `Array.from(...)` 转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,它将被视为一个空数组。 + +::: + +::: info `object` 可以是 `null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`find` 函数会按照以下方式处理 `object`: + +- 如果 `object` 是 `null` 或 `undefined`,它将被转换为一个空对象。 + +::: + +- `doesMatch`: + + - 对于数组的 `find` 重载: + + - **谓词函数** (`(item: T, index: number, arr: T[]) => unknown`): 一个函数,接受项、其索引和数组,如果项符合条件则返回真值。 + - **部分对象** (`Partial`): 指定要匹配的属性的部分对象。 + - **属性-值对** (`[keyof T, unknown]`): 一个数组,第一个元素是属性键,第二个元素是要匹配的值。 + - **属性名称** (`PropertyKey`): 要检查其真值的属性名称。 + + - 对于对象的 `find` 重载: + - **谓词函数** (`(item: T[keyof T], index: number, object: T) => unknown`): 一个函数,接受项、其键和对象,如果项符合条件则返回真值。 + - **部分值** (`Partial`): 用于与对象的值进行匹配的部分值。 + - **属性-值对** (`[keyof T[keyof T], unknown]`): 一个数组,第一个元素是属性键,第二个元素是要匹配的值。 + - **属性名称** (`PropertyKey`): 要检查其真值的属性名称。 + +- `fromIndex` (`number`): 搜索开始的位置。默认为 `0`。 + +### 返回 + +(`T | undefined`): 第一个具有指定属性值的项,如果没有找到匹配项,则返回 `undefined`。 + +## 示例 + +### 数组 + +```typescript +import { findLast } from 'es-toolkit/compat'; + +// 使用谓词函数 +const items = [1, 2, 3, 4, 5]; +const result = findLast(items, item => item > 3); +console.log(result); // 5 + +// 使用部分对象 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// 使用属性-值对 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// 使用属性名称 +const items = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, +]; +const result = findLast(items, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` + +### 对象 + +```typescript +import { findLast } from 'es-toolkit/compat'; + +// 使用谓词函数 +const obj = { a: 1, b: 2, c: 3 }; +const result = findLast(obj, item => item > 2); +console.log(result); // 3 + +// 使用部分对象 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = findLast(obj, { name: 'Bob' }); +console.log(result); // { id: 2, name: 'Bob' } + +// 使用属性-值对 +const items = { alice: { id: 1, name: 'Alice' }, bob: { id: 2, name: 'Bob' } }; +const result = findLast(items, ['name', 'Alice']); +console.log(result); // { id: 1, name: 'Alice' } + +// 使用属性名称 +const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' } }; +const result = findLast(obj, 'name'); +console.log(result); // { id: 2, name: 'Bob' } +``` diff --git a/src/compat/array/find.ts b/src/compat/array/find.ts index 6308d1cb0..ce963e3e4 100644 --- a/src/compat/array/find.ts +++ b/src/compat/array/find.ts @@ -1,6 +1,4 @@ -import { property } from '../object/property.ts'; -import { matches } from '../predicate/matches.ts'; -import { matchesProperty } from '../predicate/matchesProperty.ts'; +import { iteratee } from '../util/iteratee.ts'; /** * Finds the first item in an array that matches the given predicate function. @@ -183,7 +181,7 @@ export function find>( */ export function find( source: ArrayLike | Record | null | undefined, - doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | PropertyKey, + _doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | PropertyKey, fromIndex = 0 ): T | undefined { if (!source) { @@ -193,41 +191,23 @@ export function find( fromIndex = Math.max(source.length + fromIndex, 0); } + const doesMatch = iteratee(_doesMatch); const values = Array.isArray(source) ? source.slice(fromIndex) : Object.values(source).slice(fromIndex); - switch (typeof doesMatch) { - case 'function': { - if (!Array.isArray(source)) { - const keys = Object.keys(source) as Array; + if (typeof doesMatch === 'function' && !Array.isArray(source)) { + const keys = Object.keys(source).slice(fromIndex) as Array; - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const value = source[key] as T; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = source[key] as T; - if (doesMatch(value, key as number, source)) { - return value; - } - } - - return undefined; + if (doesMatch(value, key as number, source)) { + return value; } - - return values.find(doesMatch); } - case 'object': { - if (Array.isArray(doesMatch) && doesMatch.length === 2) { - const key = doesMatch[0]; - const value = doesMatch[1]; - return values.find(matchesProperty(key, value)); - } else { - return values.find(matches(doesMatch)); - } - } - case 'symbol': - case 'number': - case 'string': { - return values.find(property(doesMatch)); - } + return undefined; } + + return values.find(doesMatch); } diff --git a/src/compat/array/findLast.spec.ts b/src/compat/array/findLast.spec.ts new file mode 100644 index 000000000..315735f83 --- /dev/null +++ b/src/compat/array/findLast.spec.ts @@ -0,0 +1,158 @@ +import { describe, expect, it } from 'vitest'; +import * as lodashStable from 'es-toolkit/compat'; +import { findLast } from './findLast'; +import { args } from '../_internal/args'; +import { empties } from '../_internal/empties'; +import { falsey } from '../_internal/falsey'; + +describe('findLast', () => { + const func = findLast; + + const objects = [ + { a: 0, b: 0 }, + { a: 1, b: 1 }, + { a: 2, b: 2 }, + ]; + + const expected = [objects[2], undefined, objects[2]]; + + it(`should return the found value`, () => { + expect(func(objects, object => object.a)).toEqual(expected[0]); + }); + + it(`should return \`${expected[1]}\` if value is not found`, () => { + expect(func(objects, object => object.a === 3)).toEqual(expected[1]); + }); + + it(`should work with \`_.matches\` shorthands`, () => { + expect(func(objects, { b: 2 })).toBe(expected[2]); + }); + + it(`should work with \`_.matchesProperty\` shorthands`, () => { + expect(func(objects, ['b', 2])).toBe(expected[2]); + }); + + it(`should work with \`_.property\` shorthands`, () => { + expect(func(objects, 'b')).toBe(expected[0]); + }); + + it(`should return \`${expected[1]}\` for empty collections`, () => { + const emptyValues = empties; + const expecting = lodashStable.map(emptyValues, lodashStable.constant(expected[1])); + + const actual = lodashStable.map(emptyValues, value => { + try { + return func(value, { a: 3 } as any); + } catch (e) { + // + } + }); + + expect(actual).toEqual(expecting); + }); + + it(`should provide correct \`predicate\` arguments for arrays`, () => { + let args: any; + const array = ['a']; + + func(array, function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }); + + expect(args).toEqual(['a', 0, array]); + }); + + it(`should work with an object for \`collection\``, () => { + const actual = findLast({ a: 1, b: 2, c: 3 }, n => n < 3); + + const expected = 2; + + expect(actual).toBe(expected); + }); + + it(`should provide correct \`predicate\` arguments for objects`, () => { + let args: any; + const object = { a: 1 }; + + findLast(object, function () { + // eslint-disable-next-line + args || (args = Array.prototype.slice.call(arguments)); + }); + + expect(args).toEqual([1, 'a', object]); + }); + + const resolve = lodashStable.curry(lodashStable.eq); + + lodashStable.each( + { + 'an `arguments` object': args, + 'an array': [1, 2, 3], + }, + (collection, key) => { + const values = lodashStable.toArray(collection); + + it(`should work with ${key} and a positive \`fromIndex\``, () => { + const expected = [values[1], undefined]; + + const actual = [findLast(collection, resolve(values[1]), 1), findLast(collection, resolve(values[2]), 1)]; + + expect(actual).toEqual(expected); + }); + + it(`should work with ${key} and a \`fromIndex\` >= \`length\``, () => { + const indexes = [4, 6, 2 ** 32, Infinity]; + + const expected = lodashStable.map(indexes, lodashStable.constant([values[0], undefined, undefined])); + + const actual = lodashStable.map(indexes, fromIndex => [ + findLast(collection, resolve(1), fromIndex), + findLast(collection, resolve(undefined), fromIndex), + findLast(collection, resolve(''), fromIndex), + ]); + + expect(actual).toEqual(expected); + }); + + it(`should work with ${key} and treat falsey \`fromIndex\` values correctly`, () => { + const expected = lodashStable.map(falsey, value => (value === undefined ? values[3] : undefined)); + + const actual = lodashStable.map(falsey, fromIndex => + findLast(collection as any, resolve(values[3]), fromIndex as any) + ); + + expect(actual).toEqual(expected); + }); + + it(`should work with ${key} and coerce \`fromIndex\` to an integer`, () => { + const expected = [values[0], values[0], undefined]; + + const actual = [ + findLast(collection, resolve(values[0]), 0.1), + findLast(collection, resolve(values[0]), NaN), + findLast(collection, resolve(values[2]), '1' as any), + ]; + + expect(actual).toEqual(expected); + }); + + it(`should work with ${key} and a negative \`fromIndex\``, () => { + const expected = [values[1], undefined]; + + const actual = [findLast(collection, resolve(values[1]), -2), findLast(collection, resolve(values[2]), -2)]; + + expect(actual).toEqual(expected); + }); + + it(`should work with ${key} and a negative \`fromIndex\` <= \`-length\``, () => { + const indexes = [-4, -6, -Infinity]; + const expected = lodashStable.map(indexes, lodashStable.constant(values[0])); + + const actual = lodashStable.map(indexes, fromIndex => findLast(collection, resolve(values[0]), fromIndex)); + + expect(actual).toEqual(expected); + }); + } + ); +}); diff --git a/src/compat/array/findLast.ts b/src/compat/array/findLast.ts new file mode 100644 index 000000000..de920f1b5 --- /dev/null +++ b/src/compat/array/findLast.ts @@ -0,0 +1,226 @@ +import { iteratee } from '../util/iteratee.ts'; +import { toInteger } from '../util/toInteger.ts'; + +/** + * Finds the last item in an array that matches the given predicate function. + * + * @template T + * @param {ArrayLike | null | undefined} arr - The array to search through. + * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. + * @param {number} [fromIndex=arr.length-1] - The index to start the search from, defaults to arr.length-1. + * @returns {T | undefined} - The last item that matches the predicate, or `undefined` if no match is found. + * + * @example + * // Using a predicate function + * const items = [1, 2, 3, 4, 5]; + * const result = findLast(items, (item) => item > 3); + * console.log(result); // 5 + */ +export function findLast( + arr: ArrayLike | null | undefined, + doesMatch: (item: T, index: number, arr: readonly T[]) => unknown, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an array that matches the given partial object. + * + * @template T + * @param {ArrayLike | null | undefined} arr - The array to search through. + * @param {Partial} doesMatch - A partial object that specifies the properties to match. + * @param {number} [fromIndex=arr.length-1] - The index to start the search from, defaults to arr.length-1. + * @returns {T | undefined} - The last item that matches the partial object, or `undefined` if no match is found. + * + * @example + * // Using a partial object + * const items = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Bob' }]; + * const result = findLast(items, { name: 'Bob' }); + * console.log(result); // { id: 3, name: 'Bob' } + */ +export function findLast( + arr: ArrayLike | null | undefined, + doesMatch: Partial, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an array that matches a property with a specific value. + * + * @template T + * @param {ArrayLike | null | undefined} arr - The array to search through. + * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. + * @param {number} [fromIndex=arr.length-1] - The index to start the search from, defaults to arr.length-1. + * @returns {T | undefined} - The last item that has the specified property value, or `undefined` if no match is found. + * + * @example + * // Using a property-value pair + * const items = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Alice' }]; + * const result = findLast(items, ['name', 'Alice']); + * console.log(result); // { id: 3, name: 'Alice' } + */ +export function findLast( + arr: ArrayLike | null | undefined, + doesMatchProperty: [keyof T, unknown], + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an array that has a specific property, where the property name is provided as a PropertyKey. + * + * @template T + * @param {ArrayLike | null | undefined} arr - The array to search through. + * @param {PropertyKey} propertyToCheck - The property name to check. + * @param {number} [fromIndex=arr.length-1] - The index to start the search from, defaults to arr.length-1. + * @returns {T | undefined} - The last item that has the specified property, or `undefined` if no match is found. + * + * @example + * // Using a property name + * const items = [{ id: 1, name: 'Alice' }, { id: 2 }, { id: 3, name: 'Bob' }]; + * const result = findLast(items, 'name'); + * console.log(result); // { id: 3, name: 'Bob' } + */ +export function findLast( + arr: ArrayLike | null | undefined, + propertyToCheck: PropertyKey, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an object that matches the given predicate function. + * + * @template T + * @param {T | null | undefined} object - The object to search through. + * @param {(item: T[keyof T], index: number, arr: T) => unknown} doesMatch - A function that takes an item, its key, and the object, and returns a truthy value if the item matches the criteria. + * @param {number} [fromIndex=Object.keys(object).length-1] - The index to start the search from, defaults to Object.keys(object).length-1. + * @returns {T | undefined} - The last property value that matches the predicate, or `undefined` if no match is found. + * + * @example + * // Using a predicate function + * const obj = { a: 1, b: 2, c: 3 }; + * const result = findLast(obj, (item) => item > 1); + * console.log(result); // 3 + */ +export function findLast>( + object: T | null | undefined, + doesMatch: (item: T[keyof T], index: keyof T, object: T) => unknown, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an object that matches the given partial value. + * + * @template T + * @param {T | null | undefined} object - The object to search through. + * @param {Partial} doesMatch - A partial value to match against the values of the object. + * @param {number} [fromIndex=Object.keys(object).length-1] - The index to start the search from, defaults to Object.keys(object).length-1. + * @returns {T | undefined} - The last property value that matches the partial value, or `undefined` if no match is found. + * + * @example + * // Using a partial value + * const obj = { a: { id: 1, name: 'Bob' }, b: { id: 2, name: 'Alice' }, c: { id: 3, name: 'Bob' } }; + * const result = findLast(obj, { name: 'Bob' }); + * console.log(result); // { id: 3, name: 'Bob' } + */ +export function findLast>( + object: T | null | undefined, + doesMatch: Partial, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an object that matches a property with a specific value. + * + * @template T + * @param {T | null | undefined} object - The object to search through. + * @param {[keyof T[keyof T], unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. + * @param {number} [fromIndex=Object.keys(object).length-1] - The index to start the search from, defaults to Object.keys(object).length-1. + * @returns {T | undefined} - The last item that has the specified property value, or `undefined` if no match is found. + * + * @example + * // Using a property-value pair + * const items = { a: { id: 1, name: 'Alice' }, b: { id: 2, name: 'Bob' }, c: { id: 3, name: 'Alice' } }; + * const result = findLast(items, ['name', 'Alice']); + * console.log(result); // { id: 3, name: 'Alice' } + */ +export function findLast>( + object: T | null | undefined, + doesMatchProperty: [keyof T[keyof T], unknown], + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an object that has a specific property, where the property name is provided as a PropertyKey. + * + * @template T + * @param {T | null | undefined} object - The object to search through. + * @param {PropertyKey} propertyToCheck - The property name to check. + * @param {number} [fromIndex=Object.keys(object).length-1] - The index to start the search from, defaults to Object.keys(object).length-1. + * @returns {T | undefined} - The last property value that has the specified property, or `undefined` if no match is found. + * + * @example + * // Using a property name + * const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2 }, c: { id: 3, name: 'Bob' } }; + * const result = findLast(obj, 'name'); + * console.log(result); // { id: 3, name: 'Bob' } + */ +export function findLast>( + object: T | null | undefined, + propertyToCheck: PropertyKey, + fromIndex?: number +): T | undefined; + +/** + * Finds the last item in an object that has a specific property, where the property name is provided as a PropertyKey. + * + * @template T + * @param {ArrayLike | Record | null | undefined} source - The source array or object to search through. + * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | PropertyKey} doesMatch - The criteria to match. It can be a function, a partial object, a key-value pair, or a property name. + * @param {number} [fromIndex] - The index to start the search from, defaults to source.length-1 for arrays or Object.keys(source).length-1 for objects. + * @returns {T | undefined} - The last property value that has the specified property, or `undefined` if no match is found. + * + * @example + * // Using a property name + * const obj = { a: { id: 1, name: 'Alice' }, b: { id: 2 }, c: { id: 3, name: 'Bob' } }; + * const result = findLast(obj, 'name'); + * console.log(result); // { id: 3, name: 'Bob' } + */ +export function findLast( + source: ArrayLike | Record | null | undefined, + _doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | PropertyKey, + fromIndex?: number +): T | undefined { + if (!source) { + return undefined; + } + + const length = Array.isArray(source) ? source.length : Object.keys(source).length; + + fromIndex = toInteger(fromIndex ?? length - 1); + + if (fromIndex < 0) { + fromIndex = Math.max(length + fromIndex, 0); + } else { + fromIndex = Math.min(fromIndex, length - 1); + } + + const doesMatch = iteratee(_doesMatch); + + const values = Array.isArray(source) ? source.slice(0, fromIndex + 1) : Object.values(source).slice(0, fromIndex + 1); + + if (typeof doesMatch === 'function' && !Array.isArray(source)) { + const keys = Object.keys(source).slice(0, fromIndex + 1) as Array; + + for (let i = fromIndex; i >= 0; i--) { + const key = keys[i]; + const value = source[key] as T; + + if (doesMatch(value, key as number, source)) { + return value; + } + } + + return undefined; + } + + return values.findLast(doesMatch); +} diff --git a/src/compat/index.ts b/src/compat/index.ts index 3ea509fa8..6cee3fa37 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -40,6 +40,7 @@ export { fill } from './array/fill.ts'; export { filter } from './array/filter.ts'; export { find } from './array/find.ts'; export { findIndex } from './array/findIndex.ts'; +export { findLast } from './array/findLast.ts'; export { findLastIndex } from './array/findLastIndex.ts'; export { flatten } from './array/flatten.ts'; export { flattenDeep } from './array/flattenDeep.ts';