diff --git a/.changeset/purple-pots-taste.md b/.changeset/purple-pots-taste.md
new file mode 100644
index 000000000..c7be1ed8f
--- /dev/null
+++ b/.changeset/purple-pots-taste.md
@@ -0,0 +1,5 @@
+---
+'@modern-kit/utils': patch
+---
+
+imp(utils): difference 성능 개선 및 문서 개선 - @ssi02014
diff --git a/docs/docs/utils/array/difference.md b/docs/docs/utils/array/difference.md
index 67777cb30..281615755 100644
--- a/docs/docs/utils/array/difference.md
+++ b/docs/docs/utils/array/difference.md
@@ -2,7 +2,7 @@
첫번째 배열을 기준으로 두번째 배열과 `중복된 요소를 제외해 고유한 값만을 갖는` 새로운 배열을 반환하는 함수입니다.
-기본적으로 `원시 값`에 대해서만 중복 요소를 판단하며, 필요 시 3번째 인자인 `iteratee` 함수 결과로 중복 요소임을 판단 할 수 있습니다.
+기본적으로 `원시 값`에 대해서만 중복 요소를 판단하지만, `iteratee` 함수를 제공하여 각 요소를 변환한 후 결과 값을 기반으로 비교할 수 있습니다.
@@ -13,13 +13,23 @@
- `hz`: 초당 작업 수
- `mean`: 평균 응답 시간(ms)
+### Default
+|이름|hz|mean|성능|
+|------|---|---|---|
+|modern-kit/difference|9,251,611.33|0.0001|`fastest`|
+|lodash/difference|4,113,377.61|0.0002|`slowest`|
+
+- **modern-kit/difference**
+ - `2.25x` faster than **lodash/difference**
+
+### with iteratee
|이름|hz|mean|성능|
|------|---|---|---|
-|modern-kit/difference|2,768,995.87|0.0004|`fastest`|
-|lodash/differenceBy|1,538,329.26|0.0007|`slowest`|
+|modern-kit/difference|11,133,900.99|0.0001|`fastest`|
+|lodash/differenceBy|3,211,808.45|0.0002|`slowest`|
- **modern-kit/difference**
- - `1.80x` faster than lodash/differenceBy
+ - `3.47x` faster than **lodash/differenceBy**
## Interface
```ts title="typescript"
@@ -35,26 +45,32 @@ const difference: (
```ts title="typescript"
import { difference } from '@modern-kit/utils';
-difference([1, 2, 3, 4], [1, 2, 3, 5]); // [4]
+const arr1 = [1, 2, 3, 4];
+const arr2 = [2, 4];
+
+difference(arr1, arr2); // [1, 3]
```
### Iteratee
```ts title="typescript"
import { difference } from '@modern-kit/utils';
-const firstArr = [
+const arr1 = [
{ id: 1, name: 'john' },
{ id: 2, name: 'dylan' },
+ { id: 3, name: 'modern' },
+ { id: 4, name: 'gromit' },
];
-const secondArr = [
- { id: 1, name: 'john' },
- { id: 3, name: 'gromit' },
+const arr2 = [
+ { id: 2, name: 'john' },
+ { id: 4, name: 'gromit' },
];
-difference(firstArr, secondArr, (item) => item.id);
+difference(arr1, arr2, (item) => item.id);
/*
[
- { id: 2, name: 'dylan' },
+ { id: 1, name: 'john' },
+ { id: 3, name: 'modern' },
]
*/
```
\ No newline at end of file
diff --git a/docs/docs/utils/string/reverseString.md b/docs/docs/utils/string/reverseString.md
index aebc7e121..82457327c 100644
--- a/docs/docs/utils/string/reverseString.md
+++ b/docs/docs/utils/string/reverseString.md
@@ -11,7 +11,7 @@
## Interface
```ts title="typescript"
-const reverseString: (value: string) => string
+function reverseString(value: string): string
```
## Usage
diff --git a/packages/utils/src/array/compact/index.ts b/packages/utils/src/array/compact/index.ts
index 8c1fb352d..314830822 100644
--- a/packages/utils/src/array/compact/index.ts
+++ b/packages/utils/src/array/compact/index.ts
@@ -18,7 +18,9 @@ type Retained = Exclude;
export function compact(arr: T[] | readonly T[]): Retained[] {
const result: Retained[] = [];
- for (const item of arr) {
+ for (let i = 0; i < arr.length; i++) {
+ const item = arr[i];
+
if (item) {
result.push(item as Retained);
}
diff --git a/packages/utils/src/array/difference/difference.bench.ts b/packages/utils/src/array/difference/difference.bench.ts
index 0649f7409..81ca543b3 100644
--- a/packages/utils/src/array/difference/difference.bench.ts
+++ b/packages/utils/src/array/difference/difference.bench.ts
@@ -1,16 +1,31 @@
import { bench, describe } from 'vitest';
-import { differenceBy as differenceByLodash } from 'lodash-es';
+import {
+ differenceBy as differenceByLodash,
+ difference as differenceLodash,
+} from 'lodash-es';
import { difference } from '.';
-const createTestArr = (length: number) => {
- return Array.from({ length }, () => ({
- id: Math.floor(Math.random() * 10),
- }));
-};
-
describe('difference', () => {
- const arr1 = createTestArr(20);
- const arr2 = createTestArr(10);
+ const arr1 = [1, 2, 3, 4, 5];
+ const arr2 = [2, 3, 4];
+ bench('modern-kit/difference', () => {
+ difference(arr1, arr2);
+ });
+
+ bench('lodash/difference', () => {
+ differenceLodash(arr1, arr2);
+ });
+});
+
+describe('difference with iteratee', () => {
+ const arr1 = [
+ { id: 1, name: 'john' },
+ { id: 2, name: 'gromit' },
+ ];
+ const arr2 = [
+ { id: 1, name: 'john' },
+ { id: 3, name: 'dylan' },
+ ];
bench('modern-kit/difference', () => {
difference(arr1, arr2, (item) => item.id);
diff --git a/packages/utils/src/array/difference/index.ts b/packages/utils/src/array/difference/index.ts
index 66cbd6941..2fd54d430 100644
--- a/packages/utils/src/array/difference/index.ts
+++ b/packages/utils/src/array/difference/index.ts
@@ -1,6 +1,7 @@
/**
* @description 첫 번째 배열에서 두 번째 배열에 없는 요소들을 반환하는 함수입니다.
- * 선택적으로 제공된 `iteratee` 함수를 사용하여 각 요소를 변환한 후 비교할 수 있습니다.
+ *
+ * `iteratee` 함수를 제공하여 각 요소를 변환한 후 결과 값을 기반으로 비교할 수 있습니다.
*
* @template T - 첫 번째 배열의 요소 타입입니다.
* @template U - `iteratee` 함수가 반환하는 타입으로, 기본값은 `T`와 같습니다.
@@ -38,8 +39,9 @@ export function difference(
iteratee ? secondArr.map(iteratee) : secondArr
);
- for (const item of firstArr) {
- const mappedItem = iteratee ? iteratee(item) : item;
+ for (let i = 0; i < firstArr.length; i++) {
+ const item = firstArr[i];
+ const mappedItem = iteratee ? iteratee(firstArr[i]) : firstArr[i];
if (!secondSet.has(mappedItem)) {
result.push(item);
diff --git a/packages/utils/src/string/reverseString/index.ts b/packages/utils/src/string/reverseString/index.ts
index 1643868ff..d56c896ad 100644
--- a/packages/utils/src/string/reverseString/index.ts
+++ b/packages/utils/src/string/reverseString/index.ts
@@ -1,4 +1,13 @@
-export function reverseString(value: string) {
+/**
+ * @description 주어진 문자열을 뒤집어 반환하는 함수입니다.
+ *
+ * @param {string} value - 뒤집을 문자열입니다.
+ * @returns {string} 뒤집힌 문자열입니다. 입력이 없거나 빈 문자열인 경우, 빈 문자열을 반환합니다.
+ *
+ * @example
+ * reverseString("hello"); // "olleh"
+ */
+export function reverseString(value: string): string {
if (!value) return '';
return [...value].reverse().join('');
diff --git a/packages/utils/src/string/reverseString/reverseString.spec.ts b/packages/utils/src/string/reverseString/reverseString.spec.ts
index 3bafb1dd7..20dad26f2 100644
--- a/packages/utils/src/string/reverseString/reverseString.spec.ts
+++ b/packages/utils/src/string/reverseString/reverseString.spec.ts
@@ -2,27 +2,21 @@ import { describe, it, expect } from 'vitest';
import { reverseString } from '.';
describe('reverseString', () => {
- it('should return the empty string for a invalid value', () => {
- const reversedString = reverseString(undefined as unknown as string);
-
- expect(reversedString).toBe('');
- });
-
- it('should return the reversed string for a normal string', () => {
+ it('주어진 문자열에 대해 반전된 문자열을 반환해야 합니다.', () => {
const normalString = 'ABC가나다';
const reversedString = reverseString(normalString);
expect(reversedString).toBe('다나가CBA');
});
- it('should return the reversed string for a string with special characters', () => {
+ it('특수 문자가 포함된 문자열에 대해 반전된 문자열을 반환해야 합니다.', () => {
const stringWithSpecialCharacter = 'A!B@C';
const reversedString = reverseString(stringWithSpecialCharacter);
expect(reversedString).toBe('C@B!A');
});
- it('should return the reversed string for a string with unicode characters', () => {
+ it('유니코드 문자가 포함된 문자열에 대해 반전된 문자열을 반환해야 합니다.', () => {
const stringWithUnicodeCharacter = 'foo 𝌆 bar';
const reversedString = reverseString(stringWithUnicodeCharacter);
diff --git a/packages/utils/src/validator/isEqual/isEqual.spec.ts b/packages/utils/src/validator/isEqual/isEqual.spec.ts
index 4e55a3781..d26d1e511 100644
--- a/packages/utils/src/validator/isEqual/isEqual.spec.ts
+++ b/packages/utils/src/validator/isEqual/isEqual.spec.ts
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import { isEqual } from '.';
describe('isEqual', () => {
- it('should return true if primitive types are deeply equal', () => {
+ it('원시 값을 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
expect(isEqual(1, 1)).toBeTruthy();
expect(isEqual('a', 'a')).toBeTruthy();
@@ -10,7 +10,7 @@ describe('isEqual', () => {
expect(isEqual('a', 'b')).toBeFalsy();
});
- it('should return true if objects or arrays are deeply equal', () => {
+ it('객체/배열을 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
expect(isEqual({ a: 1, b: { c: 1 } }, { a: 1, b: { c: 1 } })).toBeTruthy();
expect(
isEqual(
@@ -45,7 +45,7 @@ describe('isEqual', () => {
).toBeFalsy();
});
- it('should return true if function are deeply equal', () => {
+ it('함수를 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
const origin = () => 1;
const target = origin;
expect(
@@ -63,7 +63,7 @@ describe('isEqual', () => {
).toBeFalsy();
});
- it('should return true if two objects with circular references are deeply equal', () => {
+ it('순환 참조를 가진 두 객체가 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
const objA: any = { a: 1 };
const objB: any = { a: 1 };
@@ -73,13 +73,13 @@ describe('isEqual', () => {
expect(isEqual(objA, objB)).toBeTruthy();
});
- it('should return true if null or undefined are deeply equal', () => {
+ it('null/undefined/NaN를 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
expect(isEqual(null, null)).toBeTruthy();
expect(isEqual(undefined, undefined)).toBeTruthy();
expect(isEqual(null, undefined)).toBeFalsy();
});
- it('should return true if Set or Map are deeply equal', () => {
+ it('Set/Map 깊은 비교 시 동일하면 true, 동일하지 않으면 false를 반환해야 합니다.', () => {
// truthy
expect(
isEqual(
@@ -130,18 +130,6 @@ describe('isEqual', () => {
it('should return true for identical arrays with different element order', () => {
const arr1 = [1, 2, 3];
const arr2 = [3, 2, 1];
- expect(isEqual(new Set(arr1), new Set(arr2))).toBe(false);
- });
-
- it('should return true for NaN values', () => {
- expect(isEqual(NaN, NaN)).toBe(true);
- });
-
- it('should return false for NaN and a number', () => {
- expect(isEqual(NaN, 1)).toBe(false);
- });
-
- it('should return false for NaN and undefined', () => {
- expect(isEqual(NaN, undefined)).toBe(false);
+ expect(isEqual(new Set(arr1), new Set(arr2))).toBeFalsy();
});
});
diff --git a/packages/utils/src/validator/isEqual/isEqual.utils.ts b/packages/utils/src/validator/isEqual/isEqual.utils.ts
index 7af5f961f..6b773e06c 100644
--- a/packages/utils/src/validator/isEqual/isEqual.utils.ts
+++ b/packages/utils/src/validator/isEqual/isEqual.utils.ts
@@ -12,7 +12,9 @@ const compareObjectOrArray = (
return false;
}
- for (const key of sourceKeys) {
+ for (let i = 0; i < sourceKeys.length; i++) {
+ const key = sourceKeys[i];
+
if (
!targetKeys.includes(key) ||
!isEqualInternal(source[key], target[key], visited)