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(differenceWith): add differenceWith to compat #859

Merged
merged 3 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions benchmarks/performance/differenceWith.bench.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { bench, describe } from 'vitest';
import { differenceWith as differenceWithToolkit_ } from 'es-toolkit';
import { differenceWith as differenceWithCompatToolkit_ } from 'es-toolkit/compat';
import { differenceWith as differenceWithLodash_ } from 'lodash';

const differenceWithToolkit = differenceWithToolkit_;
const differenceWithCompatToolkit = differenceWithCompatToolkit_;
const differenceWithLodash = differenceWithLodash_;

describe('differenceWith', () => {
bench('es-toolkit/differenceWith', () => {
differenceWithToolkit([1.2, 2.3, 3.4], [1.2], (x, y) => Math.floor(x) === Math.floor(y));
});

bench('es-toolkit/compat/differenceWith', () => {
differenceWithCompatToolkit([1.2, 2.3, 3.4], [1.2], (x, y) => Math.floor(x) === Math.floor(y));
});

bench('lodash/differenceWith', () => {
differenceWithLodash([1.2, 2.3, 3.4], [1.2], (x, y) => Math.floor(x) === Math.floor(y));
});
Expand All @@ -23,6 +29,10 @@ describe('differenceWith/largeArray', () => {
differenceWithToolkit(largeArray, largeArray2, (x, y) => Math.floor(x) === Math.floor(y));
});

bench('es-toolkit/compat/differenceWith', () => {
differenceWithCompatToolkit(largeArray, largeArray2, (x, y) => Math.floor(x) === Math.floor(y));
});

bench('lodash/differenceWith', () => {
differenceWithLodash(largeArray, largeArray2, (x, y) => Math.floor(x) === Math.floor(y));
});
Expand Down
37 changes: 37 additions & 0 deletions docs/ja/reference/array/differenceWith.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,40 @@ const areItemsEqual = (a, b) => a.id === b;
const result = differenceWith(array1, array2, areItemsEqual);
// resultは[{ id: 1 }, { id: 3 }]になります。idが2の要素は2番目の配列の要素と同じと見なされ、結果から除外されます。
```

## Lodashとの互換性

`es-toolkit/compat`から`differenceWith`をインポートすると、lodashと互換性があります。

- `differenceWith`は、最初の配列と比較するための複数の配列を受け取ることができます。
- `differenceWith`は、配列風オブジェクト(Array-like object)も引数として受け取ることができます。
- `differenceWith`はカスタム比較関数を省略することができます。省略した場合、デフォルトで[SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero)アルゴリズムが使用されます。

```typescript
import { differenceWith } from 'es-toolkit/compat';

// 例1: 複数の配列を比較し、比較関数を使用する場合
const array = [{ id: 1 }, { id: 2 }, { id: 3 }];
const values1 = [{ id: 2 }];
const values2 = [{ id: 3 }];
const comparator = (a, b) => a.id === b.id;

const result = differenceWith(array, values1, values2, comparator);
// 結果は [{ id: 1 }] です。この要素は比較基準に基づいて最初の配列にのみ存在します。

// 例2: 配列風オブジェクトを引数として受け取り、比較関数を使用する場合
const array = { 0: { id: 1 }, 1: { id: 2 }, 2: { id: 3 }, length: 3 };
const values = { 0: { id: 2 }, 1: { id: 3 }, length: 2 };
const comparator = (a, b) => a.id === b.id;

const result2 = differenceWith(array, values, comparator);
// 結果は [{ id: 1 }] です。この要素は比較基準に基づいて最初の配列風オブジェクトにのみ存在します。

// 例3: カスタム比較関数を省略
const array = [1, 2, 3];
const values1 = [2];
const values2 = [3];

const result3 = differenceWith(array, values1, values2);
// 結果は [1] です。この要素はすべての配列に一意に存在します。
```
2 changes: 1 addition & 1 deletion docs/ja/reference/compat/util/gte.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ function gte(value: unknown, other: unknown): boolean;
gte(3, 1); // => true
gte(3, 3); // => true
gte(1, 3); // => false
```
```
37 changes: 37 additions & 0 deletions docs/ko/reference/array/differenceWith.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,40 @@ const areItemsEqual = (a, b) => a.id === b;
const result = differenceWith(array1, array2, areItemsEqual);
// result는 [{ id: 1 }, { id: 3 }]가 돼요. id가 2인 요소들은 동일하다고 간주돼서 결과에서 제외돼요.
```

## Lodash와 호환성

`es-toolkit/compat`에서 `differenceWith`를 가져오면 lodash와 완전히 호환돼요.

- `differenceWith`는 첫 번째 배열과 비교하기 위해 여러 배열을 받을 수 있어요.
- `differenceWith`는 유사 배열 객체를 인수로 받을 수 있어요.
- `differenceWith는` 사용자 정의 비교 함수를 생략할 수 있어요. 생략하면 기본적으로 [SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero) 알고리즘이 사용돼요.

```typescript
import { differenceWith } from 'es-toolkit/compat';

// 예제 1: 여러 배열과 비교하며, 비교 함수를 사용하는 경우
const array = [{ id: 1 }, { id: 2 }, { id: 3 }];
const values1 = [{ id: 2 }];
const values2 = [{ id: 3 }];
const comparator = (a, b) => a.id === b.id;

const result = differenceWith(array, values1, values2, comparator);
// 결과는 [{ id: 1 }]이에요. 이 요소는 비교 기준에 따라 첫 번째 배열에만 남아 있어요.

// 예제 2: 배열과 유사한 객체를 인수로 받고, 비교 함수를 사용하는 경우
const array = { 0: { id: 1 }, 1: { id: 2 }, 2: { id: 3 }, length: 3 };
const values = { 0: { id: 2 }, 1: { id: 3 }, length: 2 };
const comparator = (a, b) => a.id === b.id;

const result2 = differenceWith(array, values, comparator);
// 결과는 [{ id: 1 }]이에요. 이 요소는 비교 기준에 따라 첫 번째 유사 배열 객체만 남아 있어요.

// 예제 3: 사용자 정의 비교 함수 생략
const array = [1, 2, 3];
const values1 = [2];
const values2 = [3];

const result3 = differenceWith(array, values1, values2);
// 결과는 [1]이에요. 이 요소는 모든 배열에서 유일하게 존재해요.
```
4 changes: 2 additions & 2 deletions docs/ko/reference/compat/util/gte.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ function gte(value: unknown, other: unknown): boolean;
### 반환 값

(`boolean`): 값이 다른 값보다 크거나 같으면 `true`를 반환하고, 그렇지 않으면 `false`를 반환해요.
문자열.
문자열.

## 예시

```typescript
gte(3, 1); // => true
gte(3, 3); // => true
gte(1, 3); // => false
```
```
37 changes: 37 additions & 0 deletions docs/reference/array/differenceWith.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,40 @@ const areItemsEqual = (a, b) => a.id === b;
const result = differenceWith(array1, array2, areItemsEqual);
// result will be [{ id: 1 }, { id: 3 }] since the element with id 2 is considered equal to the second array's element and is excluded from the result.
```

## Lodash Compatibility

Import `differenceWith` from `es-toolkit/compat` for full compatibility with lodash.

- `differenceWith` can accept multiple arrays to be compared against the first array.
- `differenceWith` can accept array-like objects as arguments.
- `differenceWith` can omit the custom equality function. If omitted, the [SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero) algorithm will be used by default.

```typescript
import { differenceWith } from 'es-toolkit/compat';

// Example 1: Accepting multiple arrays to be compared against the first array with a comparator
const array = [{ id: 1 }, { id: 2 }, { id: 3 }];
const values1 = [{ id: 2 }];
const values2 = [{ id: 3 }];
const comparator = (a, b) => a.id === b.id;

const result = differenceWith(array, values1, values2, comparator);
// result will be [{ id: 1 }]

// Example 2: Accepting array-like objects as arguments with a comparator
const array = { 0: { id: 1 }, 1: { id: 2 }, 2: { id: 3 }, length: 3 };
const values = { 0: { id: 2 }, 1: { id: 3 }, length: 2 };
const comparator = (a, b) => a.id === b.id;

const result = differenceWith(array, values, comparator);
// result will be [{ id: 1 }]

// Example 3: Omitting the custom equality function
const array = [1, 2, 3];
const values1 = [2];
const values2 = [3];

const result = differenceWith(array, values1, values2);
// result will be [1]
```
2 changes: 1 addition & 1 deletion docs/reference/compat/util/gte.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ function gte(value: unknown, other: unknown): boolean;
gte(3, 1); // => true
gte(3, 3); // => true
gte(1, 3); // => false
```
```
37 changes: 37 additions & 0 deletions docs/zh_hans/reference/array/differenceWith.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,40 @@ const areItemsEqual = (a, b) => a.id === b;
const result = differenceWith(array1, array2, areItemsEqual);
// 结果将是 [{ id: 1 }, { id: 3 }] 因为具有 id 为 2 的元素被认为与第二个数组的元素相等,因此被排除在结果之外。
```

## Lodash 兼容性

从 `es-toolkit/compat` 导入 `differenceWith` 以获得与 lodash 的完全兼容性。

- `differenceWith` 可以接受多个数组与第一个数组进行比较。
- `differenceWith` 可以接受类数组对象作为参数。
- `differenceWith` はカスタム比較関数を省略することができます。省略された場合、デフォルトで [SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero) アルゴリズムが使用されます。

```typescript
import { differenceWith } from 'es-toolkit/compat';

// 示例 1: 接受多个数组进行比较,并使用比较函数
const array = [{ id: 1 }, { id: 2 }, { id: 3 }];
const values1 = [{ id: 2 }];
const values2 = [{ id: 3 }];
const comparator = (a, b) => a.id === b.id;

const result = differenceWith(array, values1, values2, comparator);
// 结果是 [{ id: 1 }]。根据比较标准,这个元素仅存在于第一个数组中。

// 示例 2: 接受类数组对象作为参数,并使用比较函数
const array = { 0: { id: 1 }, 1: { id: 2 }, 2: { id: 3 }, length: 3 };
const values = { 0: { id: 2 }, 1: { id: 3 }, length: 2 };
const comparator = (a, b) => a.id === b.id;

const result2 = differenceWith(array, values, comparator);
// 结果是 [{ id: 1 }]。根据比较标准,这个元素仅存在于第一个类数组对象中。

// 示例 3: 省略自定义比较函数
const array = [1, 2, 3];
const values1 = [2];
const values2 = [3];

const result3 = differenceWith(array, values1, values2);
// 结果是 [1]。这个元素在所有数组中唯一存在。
```
2 changes: 1 addition & 1 deletion docs/zh_hans/reference/compat/util/gte.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ function gte(value: unknown, other: unknown): boolean;
gte(3, 1); // => true
gte(3, 3); // => true
gte(1, 3); // => false
```
```
4 changes: 2 additions & 2 deletions src/compat/_internal/flattenArrayLike.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isArrayLikeObject } from '../predicate/isArrayLikeObject.ts';

export function flattenArrayLike<T>(values: Array<ArrayLike<T>>): T[] {
export function flattenArrayLike<T>(values: Array<ArrayLike<T> | unknown>): T[] {
const result: T[] = [];

for (let i = 0; i < values.length; i++) {
Expand All @@ -11,7 +11,7 @@ export function flattenArrayLike<T>(values: Array<ArrayLike<T>>): T[] {
}

for (let j = 0; j < arrayLike.length; j++) {
result.push(arrayLike[j]);
result.push(arrayLike[j] as T);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/compat/_internal/stubOne.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const stubOne = function () {
return 1;
};
118 changes: 118 additions & 0 deletions src/compat/array/differenceWith.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, expect, it } from 'vitest';
import { differenceWith } from './differenceWith';
import { range } from '../../math';
import { isEqual } from '../../predicate';
import { args } from '../_internal/args';
import { LARGE_ARRAY_SIZE } from '../_internal/LARGE_ARRAY_SIZE';
import { stubNaN } from '../_internal/stubNaN';
import { stubOne } from '../_internal/stubOne';
import { constant } from '../util/constant';
import { eq } from '../util/eq';
import { times } from '../util/times';
import { toString } from '../util/toString';

describe('differenceWith', () => {
/**
* @see https://github.com/lodash/lodash/blob/afcd5bc1e8801867c31a17566e0e0edebb083d0e/test/difference-methods.spec.js#L1
*/
it(`should return the difference of two arrays`, () => {
const actual = differenceWith([2, 1], [2, 3]);
expect(actual).toEqual([1]);
});

it(`should return the difference of multiple arrays`, () => {
const actual = differenceWith([2, 1, 2, 3], [3, 4], [3, 2]);
expect(actual).toEqual([1]);
});

it(`should treat \`-0\` as \`0\``, () => {
const array = [-0, 0];

const actual = array.map(value => differenceWith(array, [value]));

expect(actual).toEqual([[], []]);

expect(differenceWith([-0, 1], [1])).toEqual([-0]);
});

it(`should match \`NaN\``, () => {
expect(differenceWith([1, NaN, 3], [NaN, 5, NaN])).toEqual([1, 3]);
});

it(`should work with large arrays`, () => {
const array1: unknown[] = range(LARGE_ARRAY_SIZE + 1);
const array2: unknown[] = range(LARGE_ARRAY_SIZE);
const a = {};
const b = {};
const c = {};

array1.push(a, b, c);
array2.push(b, c, a);

expect(differenceWith(array1, array2)).toEqual([LARGE_ARRAY_SIZE]);
});

it(`should work with large arrays of \`-0\` as \`0\``, () => {
const array = [-0, 0];

const actual = array.map(value => {
const largeArray = times(LARGE_ARRAY_SIZE, constant(value));

return differenceWith(array, largeArray);
});

expect(actual).toEqual([[], []]);

const largeArray = times(LARGE_ARRAY_SIZE, stubOne);
expect(differenceWith([-0, 1], largeArray)).toEqual([-0]);
});

it(`should work with large arrays of \`NaN\``, () => {
const largeArray = times(LARGE_ARRAY_SIZE, stubNaN);
expect(differenceWith([1, NaN, 3], largeArray)).toEqual([1, 3]);
});

it(`should work with large arrays of objects`, () => {
const object1 = {};
const object2 = {};
const largeArray = times(LARGE_ARRAY_SIZE, constant(object1));

expect(differenceWith([object1, object2], largeArray)).toEqual([object2]);
});

it(`should ignore values that are not array-like`, () => {
const array = [1, null, 3];

// @ts-expect-error
expect(differenceWith(args, 3, { 0: 1 })).toEqual([1, 2, 3]);
// @ts-expect-error
expect(differenceWith(null, array, 1)).toEqual([]);
// @ts-expect-error
expect(differenceWith(array, args, null)).toEqual([null]);
});

/**
* @see https://github.com/lodash/lodash/blob/afcd5bc1e8801867c31a17566e0e0edebb083d0e/test/differenceWith.spec.js#L1
*/

it('should work with a `comparator`', () => {
const objects = [
{ x: 1, y: 2 },
{ x: 2, y: 1 },
];
const actual = differenceWith(objects, [{ x: 1, y: 2 }], isEqual);

expect(actual).toEqual([objects[1]]);
});

it('should preserve the sign of `0`', () => {
const array = [-0, 1];
const largeArray = times(LARGE_ARRAY_SIZE, stubOne);
const others = [[1], largeArray];
const expected = others.map(constant(['-0']));

const actual = others.map(other => differenceWith(array, other, eq).map(toString));

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