Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compat): Add lastIndexOf function to es-toolkit/compat #856

Merged
merged 7 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions benchmarks/bundle-size/lastIndexOf.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest';
import { getBundleSize } from './utils/getBundleSize';

describe('lastIndexOf bundle size', () => {
it('lodash-es', async () => {
const bundleSize = await getBundleSize('lodash-es', 'lastIndexOf');
expect(bundleSize).toMatchInlineSnapshot(`1586`);
});

it('es-toolkit', async () => {
const bundleSize = await getBundleSize('es-toolkit', 'lastIndexOf');
expect(bundleSize).toMatchInlineSnapshot(`235`);
});

it('es-toolkit/compat', async () => {
const bundleSize = await getBundleSize('es-toolkit/compat', 'lastIndexOf');
expect(bundleSize).toMatchInlineSnapshot(`435`);
});
});
29 changes: 29 additions & 0 deletions benchmarks/performance/lastIndexOf.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { bench, describe } from 'vitest';
import { lastIndexOf as lastIndexOfToolkitCompat_ } from 'es-toolkit/compat';
import { lastIndexOf as lastIndexOfLodash_ } from 'lodash';

const indexOfToolkitCompat = lastIndexOfToolkitCompat_;
const indexOfLodash = lastIndexOfLodash_;

describe('lastIndexOf', () => {
const largeArray = Array(1_000_000)
.fill(0)
.map((_, i) => i);
const array = [1, 2, 3, 4, NaN, '1', '2', '3', '4', 'NaN', ...largeArray];

bench('es-toolkit/compat/lastIndexOf', () => {
indexOfToolkitCompat(array, 1);
indexOfToolkitCompat(array, NaN);
indexOfToolkitCompat(array, '1');
indexOfToolkitCompat(array, 'NaN', -5);
indexOfToolkitCompat(array, 'NaN', -100);
});

bench('lodash/lastIndexOf', () => {
indexOfLodash(array, 1);
indexOfLodash(array, NaN);
indexOfLodash(array, '1');
indexOfLodash(array, 'NaN', -5);
indexOfLodash(array, 'NaN', -100);
});
});
46 changes: 46 additions & 0 deletions docs/ja/reference/array/lastIndexOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# lastIndexOf

::: info
この関数は互換性のために `es-toolkit/compat` でのみ利用可能です。代替となるネイティブ JavaScript API が存在するか、まだ完全に最適化されていない関数です。

`es-toolkit/compat` からインポートした場合、[こちら](../../../compatibility.md)で説明されているように、lodash と全く同じように動作し、同じ機能を提供します。
:::

配列内の値の最後の出現位置のインデックスを検索します。

このメソッドは `Array.prototype.lastIndexOf` と似ていますが、`NaN` 値も検索できます。
`NaN` 以外の要素の比較には厳密等価演算子(`===`)を使用します。

## シグネチャ

```typescript
function lastIndexOf<T>(array: T[], searchElement: T, fromIndex?: number): number;
```

### パラメータ

- `array` (`T[]`): 検索対象の配列。

::: info `array` は `ArrayLike<T>` または `null` または `undefined` も可能です

lodash との完全な互換性を確保するため、`lastIndexOf` 関数は `array` を以下のように処理します:

- `array` が `ArrayLike<T>` の場合、`Array.from(...)` を使用して配列に変換します。
- `array` が `null` または `undefined` の場合、空の配列として扱われます。

:::

- `searchElement` (`T`): 検索する値。
- `fromIndex` (`number`, オプション): 検索を開始するインデックス。

### 戻り値

(`number`): 配列内で値が最後に出現する位置のインデックス(0から始まる)。値が見つからない場合は `-1` を返します。

## 使用例

```typescript
const array = [1, 2, 3, NaN, 1];
lastIndexOf(array, 1); // => 4
lastIndexOf(array, NaN); // => 3
```
46 changes: 46 additions & 0 deletions docs/ko/reference/compat/array/lastIndexOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# lastIndexOf

::: info
이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요.

`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요.
:::

배열을 주어진 요소가 마지막으로 일치하는 인덱스를 찾아요.

`Array.prototype.lastIndexOf`와 거의 같게 동작하는데요, `NaN` 값을 찾을 수 있어요.
`NaN`이 아닌 요소를 비교할 때는 엄격 동등 연산(`===`)을 사용해요.

## 인터페이스

```typescript
function lastIndexOf<T>(array: T[], searchElement: T, fromIndex?: number): number;
```

### 파라미터

- `array` (`T[]`): 검색할 배열.

::: info `array`는 `ArrayLike<T>`이거나 `null` 또는 `undefined`일 수 있어요

lodash와 완전히 호환되도록 `lastIndexOf` 함수는 `array`를 다음과 같이 처리해요.

- `array`가 `ArrayLike<T>`인 경우, 배열로 변환하기 위해 `Array.from(...)`을 사용해요.
- `array`가 `null` 또는 `undefined`인 경우, 빈 배열로 간주돼요.

:::

- `searchElement` (`T`): 찾을 값.
- `fromIndex` (`number`, 선택): 검색을 시작할 인덱스.

### 반환 값

(`number`): 배열에서 주어진 값과 마지막으로 일치하는 요소의 인덱스. 일치하는 요소를 찾을 수 없으면 `-1`을 반환해요.

## 예시

```typescript
const array = [1, 2, 3, NaN, 1];
lastIndexOf(array, 3); // => 4
lastIndexOf(array, NaN); // => 3
```
46 changes: 46 additions & 0 deletions docs/reference/compat/array/lastIndexOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# lastIndexOf

::: 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 index of the last occurrence of a value in an array.

This method is similar to `Array.prototype.lastIndexOf`, but it also finds `NaN` values.
It uses strict equality (`===`) to compare elements other than `NaN`.

## Signature

```typescript
function lastIndexOf<T>(array: T[], searchElement: T, fromIndex?: number): number;
```

### Parameters

- `array` (`T[]`): The array to search.

::: info `array` can be `ArrayLike<T>` or `null` or `undefined`

To ensure full compatibility with lodash, the `lastIndexOf` function processes `array` as follows:

- If `array` is `ArrayLike<T>`, it converts it to an array using `Array.from(...)`.
- If `array` is `null` or `undefined`, it is treated as an empty array.

:::

- `searchElement` (`T`): The value to search for.
- `fromIndex` (`number`, optional): The index to start the search at.

### Returns

(`number`): The index (zero-based) of the last occurrence of the value in the array, or `-1` if the value is not found.

## Example

```typescript
const array = [1, 2, 3, NaN, 1];
lastIndexOf(array, 1); // => 4
lastIndexOf(array, NaN); // => 3
```
46 changes: 46 additions & 0 deletions docs/zh_hans/reference/compat/array/lastIndexOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# lastIndexOf

::: info
出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。

从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。
:::

查找数组中最后一个出现的值的索引。

此方法类似于 `Array.prototype.lastIndexOf`,但也能找到 `NaN` 值。
它使用严格相等 (===) 来比较 `NaN` 以外的元素。

## 签名

```typescript
function lastIndexOf<T>(array: T[], searchElement: T, fromIndex?: number): number;
```

### 参数

- `array` (`T[]`): 要搜索的数组。

::: info `array` 可以是 `ArrayLike<T>` 或 `null` 或 `undefined` 。

为了确保与 lodash 的完全兼容性,`lastIndexOf` 函数会按照以下方式处理 `array`:

- 如果 `array` 是 `ArrayLike<T>`,它会使用 `Array.from(...)` 将其转换为数组。
- 如果 `array` 是 `null` 或 `undefined`,它会被视为一个空数组。

:::

- `searchElement` (`T`): 要搜索的值。
- `fromIndex` (`number`, 可选): 开始搜索的索引。

### 返回

(`number`): 数组中最后一个出现的值的索引(以零为基准),如果未找到该值,则返回 `-1`。

## 示例

```typescript
const array = [1, 2, 3, NaN, 1];
lastIndexOf(array, 1); // => 4
lastIndexOf(array, NaN); // => 3
```
57 changes: 57 additions & 0 deletions src/compat/array/lastIndexOf.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest';
import { lastIndexOf } from './lastIndexOf';

/**
* @see https://github.com/lodash/lodash/blob/v5-wip/test/findLastIndex-and-lastIndexOf.spec.js
*/

describe('lastIndexOf', () => {
const array = [1, 2, 3, 1, 2, 3];

it(`should return the index of the last matched value`, () => {
expect(lastIndexOf(array, 3)).toBe(5);
});

it(`should work with \`NaN\``, () => {
expect(lastIndexOf([1, 2, 3, NaN, 1, 2], NaN)).toBe(3);
});

it(`should work with a positive \`fromIndex\``, () => {
expect(lastIndexOf(array, 1, 2)).toBe(0);
});

it(`should work with a \`fromIndex\` >= \`length\``, () => {
const values = [6, 8, 2 ** 32, Infinity];
const expected = values.map(() => [-1, 3]);

const actual = values.map(fromIndex => [
lastIndexOf(array, undefined, fromIndex),
lastIndexOf(array, 1, fromIndex),
]);

expect(actual).toEqual(expected);
});

it(`should work with a negative \`fromIndex\``, () => {
expect(lastIndexOf(array, 2, -3)).toBe(1);
});

it(`should work with a negative \`fromIndex\` <= \`-length\``, () => {
const values = [-6, -8, -Infinity];
const expected = values.map(() => 0);

const actual = values.map(fromIndex => lastIndexOf(array, 1, fromIndex));

expect(actual).toEqual(expected);
});

it(`should coerce \`fromIndex\` to an integer`, () => {
expect(lastIndexOf(array, 2, 4.2)).toBe(4);
});

it(`should return -1 for empty array or nullish values`, () => {
expect(lastIndexOf([], 1)).toBe(-1);
expect(lastIndexOf(null, 1)).toBe(-1);
expect(lastIndexOf(undefined, 1)).toBe(-1);
});
});
42 changes: 42 additions & 0 deletions src/compat/array/lastIndexOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { isArrayLike } from '../predicate/isArrayLike';

/**
* Finds the index of the last occurrence of a value in an array.
*
* This method is similar to `Array.prototype.lastIndexOf`, but it also finds `NaN` values.
* It uses strict equality (`===`) to compare elements.
*
* @template T - The type of elements in the array.
* @param {ArrayLike<T> | null | undefined} array - The array to search.
* @param {T} searchElement - The value to search for.
* @param {number} [fromIndex] - The index to start the search at.
* @returns {number} The index (zero-based) of the last occurrence of the value in the array, or `-1` if the value is not found.
*
* @example
* const array = [1, 2, 3, NaN, 1];
* lastIndexOf(array, 3); // => 4
* lastIndexOf(array, NaN); // => 3
*/
export function lastIndexOf<T>(array: ArrayLike<T> | null | undefined, searchElement: T, fromIndex?: number): number {
if (!isArrayLike(array) || array.length === 0) {
return -1;
}

const length = array.length;

let index = fromIndex ?? length - 1;
if (fromIndex != null) {
index = index < 0 ? Math.max(length + index, 0) : Math.min(index, length - 1);
}

// `Array.prototype.lastIndexOf` doesn't find `NaN` values, so we need to handle that case separately.
if (Number.isNaN(searchElement)) {
for (let i = index; i >= 0; i--) {
if (Number.isNaN(array[i])) {
return i;
}
}
}

return Array.from(array).lastIndexOf(searchElement, index);
}
Loading