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)