Skip to content

Commit

Permalink
fix(utils): omit, pick 인터페이스 수정 및 로직 간소화
Browse files Browse the repository at this point in the history
  • Loading branch information
ssi02014 committed Oct 16, 2024
1 parent 6561a60 commit 2adc3d3
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 89 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-bulldogs-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/utils': patch
---

fix(utils): omit, pick 인터페이스 수정 및 로직 간소화 - @ssi02014
4 changes: 2 additions & 2 deletions docs/docs/utils/array/contains.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ Object.is(NaN, 0 / 0); // true
## Interface

```ts title="typescript"
const contains: <T>(
function contains<T>(
arr: T[] | readonly T[],
value: unknown,
comparator?: (x: any, y: any) => boolean
) => value is T;
): value is T;
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/utils/array/excludeElements.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
```ts title="typescript"
const excludeElements: <T, U>(
arr: T[] | readonly T[],
excludeArr: T[] | readonly T[],
target: T[] | readonly T[],
iteratee?: ((item: T) => U) | undefined
) => T[];
```
Expand Down
18 changes: 15 additions & 3 deletions docs/docs/utils/object/omit.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@
## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/object/omit/index.ts)

## Benchmark
- `hz`: 초당 작업 수
- `mean`: 평균 응답 시간(ms)

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/omit|3,567,312.89|0.0003|`fastest`|
|lodash/omit|879,967.75|0.0011|-|

- **modern-kit/omit**
- `4.05x` faster than lodash/omit

## Interface
```ts title="typescript"
const omit: <T extends Record<PropertyKey, any>, K extends keyof T>(
function omit<T extends Record<PropertyKey, any>, K extends keyof T>(
obj: T,
keys: K[]
) => Omit<T, K>;
keys: K[] | readonly K[]
): Omit<T, K>;
```

## Usage
Expand Down
26 changes: 15 additions & 11 deletions docs/docs/utils/object/pick.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@
## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/object/pick/index.ts)

## Benchmark
- `hz`: 초당 작업 수
- `mean`: 평균 응답 시간(ms)

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/pick|5,663,602.36|0.0002|`fastest`|
|lodash/pick|1,035,576.42|0.0010|-|

- **modern-kit/pick**
- `4.05x` faster than lodash/pick

## Interface
```ts title="typescript"
type ObjectKeys<T extends Record<PropertyKey, T[keyof T]>> = Exclude<
keyof T,
symbol
>;

const pick: <
T extends Record<PropertyKey, T[keyof T]>,
K extends ObjectKeys<T>
>(
function pick<T extends Record<PropertyKey, any>, K extends keyof T>(
obj: T,
keys: K | K[]
) => Pick<Record<ObjectKeys<T>, T[ObjectKeys<T>]>, K>;
keys: K[] | readonly K[]
): Pick<T, K>;
```

## Usage
Expand Down
11 changes: 5 additions & 6 deletions packages/utils/src/array/contains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*
* @template T - 배열의 요소 타입입니다.
* @param {T[] | readonly T[]} arr - 검색할 배열입니다. 변경 불가능한 읽기 전용 배열도 허용됩니다.
* @param {U} value - 배열에서 찾고자 하는 값입니다.
* @param {(x: T, y: U) => boolean} [comparator=Object.is] - 배열의 요소와 찾고자 하는 값을 비교할 때 사용할 사용자 정의 비교 함수입니다. 기본값은 `Object.is`입니다.
* @returns {value is U} 주어진 값이 배열에 포함되어 있으면 `true`를, 그렇지 않으면 `false`를 반환합니다.
* @param {unknown} value - 배열에서 찾고자 하는 값입니다.
* @param {(x: any, y: any) => boolean} [comparator=Object.is] - 배열의 요소와 찾고자 하는 값을 비교할 때 사용할 사용자 정의 비교 함수입니다. 기본값은 `Object.is`입니다.
* @returns {value is T} 주어진 값이 배열에 포함되어 있으면 `true`를, 그렇지 않으면 `false`를 반환합니다.
*
* @example
* // 기본 비교 함수 사용 (Object.is)
Expand All @@ -18,10 +18,9 @@
* @example
* // 사용자 정의 비교 함수 사용
* const objects = [{ id: 1 }, { id: 2 }];
* const comparator = (a: { id: number }, b: { id: number }) => a.id === b.id;
*
* contains(objects, { id: 2 }, (a, b) => a.id === b.id); // true
* contains(objects, { id: 3 }, (a, b) => a.id === b.id); // false
* contains(objects, { id: 2 }, (x, y) => x.id === y.id); // true
* contains(objects, { id: 3 }, (x, y) => x.id === y.id); // false
*/
export function contains<T>(
arr: T[] | readonly T[],
Expand Down
35 changes: 30 additions & 5 deletions packages/utils/src/array/excludeElements/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
/**
* @description 주어진 배열에서 특정 요소를 제외한 배열을 반환하는 함수입니다.
* `target` 배열에 포함된 요소들이 `arr`에서 필터링되며, 선택적으로 제공되는 `iteratee` 함수를
* 사용하여 요소를 비교할 수 있습니다.
*
* @template T - 원본 배열(`arr`) 및 제외 대상 배열(`target`)의 요소 타입.
* @template U - 선택적으로 제공되는 `iteratee` 함수의 반환 타입.
*
* @param {T[] | readonly T[]} arr - 제외할 요소들을 포함한 원본 배열.
* @param {T[] | readonly T[]} target - 제외할 요소들을 포함한 배열.
* @param {(item: T) => U} [iteratee] - 요소를 비교할 때 사용하는 선택적 함수.
* 이 함수가 제공되면, 각 요소에 대해 함수의 반환값이 비교에 사용됩니다.
*
* @returns {T[]} - `target` 배열에 포함된 요소들이 제외된 원본 배열의 사본.
*
* @example
* const arr = [1, 2, 3, 4];
* const target = [2, 4];
* const result = excludeElements(arr, target);
* // 결과: [1, 3]
*
* @example
* const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];
* const target = [{ id: 2 }];
* const result = excludeElements(arr, target, item => item.id);
* // 결과: [{ id: 1 }, { id: 3 }]
*/
export function excludeElements<T, U>(
arr: T[] | readonly T[],
excludeArr: T[] | readonly T[],
target: T[] | readonly T[],
iteratee?: (item: T) => U
) {
const exclusionSet = new Set<U | T>(
iteratee ? excludeArr.map(iteratee) : excludeArr
);
): T[] {
const exclusionSet = new Set<U | T>(iteratee ? target.map(iteratee) : target);

const filterFn = iteratee
? (element: T) => !exclusionSet.has(iteratee(element))
Expand Down
25 changes: 20 additions & 5 deletions packages/utils/src/object/omit/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { deepCopy } from '../../common';

/**
* @description 주어진 객체에서 지정된 키들을 제거한 새로운 객체를 반환하는 함수입니다.
*
* @template T - 원본 객체의 타입.
* @template K - 제거할 키들의 타입, T의 키 중 일부.
*
* @param {T} obj - 키를 제거할 원본 객체.
* @param {K[] | readonly K[]} keys - 제거할 키들의 배열.
* @returns {Omit<T, K>} - 지정된 키들이 제거된 새로운 객체.
*
* @example
* const obj = { a: 1, b: 2, c: 3 };
* const result = omit(obj, ['a', 'c']);
* // { b: 2 }
*/
export function omit<T extends Record<PropertyKey, any>, K extends keyof T>(
obj: T,
keys: K[]
keys: K[] | readonly K[]
): Omit<T, K> {
const result = deepCopy(obj);
const result = { ...obj };

for (let i = 0; i < keys.length; i++) {
const key = keys[i];

for (const key of keys) {
delete result[key];
}

Expand Down
13 changes: 13 additions & 0 deletions packages/utils/src/object/omit/omit.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { bench, describe } from 'vitest';
import { omit } from '.';
import { omit as omitLodash } from 'lodash-es';

describe('omit', () => {
bench('modern-kit/omit', () => {
omit({ a: 1, b: 2, c: 3, d: 4, e: 5 }, ['a', 'c', 'e']);
});

bench('lodash/omit', () => {
omitLodash({ a: 1, b: 2, c: 3, d: 4, e: 5 }, ['a', 'c', 'e']);
});
});
26 changes: 6 additions & 20 deletions packages/utils/src/object/omit/omit.spec.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, expectTypeOf } from 'vitest';
import { omit } from '.';

describe('omit function', () => {
it('should return a new object omitting a single key from an object', () => {
const inputObj = { a: 1, b: 2, c: 3 } as const;
const omittedObj = omit(inputObj, ['b']);

expect(omittedObj).toEqual({ a: 1, c: 3 });
});

it('should return a new object omitting multiple keys from an object', () => {
const symbol = Symbol('d');
const inputObj = { a: 1, b: 2, c: 3, d: 4, [symbol]: 5 } as const;
const omittedObj = omit(inputObj, ['b', 'd', symbol]);

expect(omittedObj).toEqual({ a: 1, c: 3 });
});
const inputObj = { a: 1, b: 2, c: 3 };
const omittedObj = omit(inputObj, ['b', 'c']);

it('should return a new object that is deeply copied', () => {
const symbol = Symbol('d');
const inputObj = { a: 1, b: { x: 2, y: 3 }, c: 4, [symbol]: 5 } as const;
const omittedObj = omit(inputObj, ['a', 'c']);
expect(omittedObj).toEqual({ a: 1 });

expect(omittedObj.b).not.toBe(inputObj.b);
expect(omittedObj.b).toEqual(inputObj.b);
// type
expectTypeOf(omittedObj).toEqualTypeOf<{ a: number }>();
});
});
44 changes: 29 additions & 15 deletions packages/utils/src/object/pick/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { ObjectKeys } from '@modern-kit/types';
import { deepCopy } from '../../common/deepCopy';
import { wrapInArray } from '../../common/wrapInArray';
import { hasProperty } from '../../validator';

export function pick<
T extends Record<PropertyKey, T[keyof T]>,
K extends ObjectKeys<T>
>(obj: T, keys: K | K[]): Pick<Record<ObjectKeys<T>, T[ObjectKeys<T>]>, K> {
/**
* @description 주어진 객체에서 지정된 키들만을 선택하여 새로운 객체를 반환하는 함수입니다.
*
* @template T - 원본 객체의 타입.
* @template K - 선택할 키들의 타입, T의 키 중 일부.
*
* @param {T} obj - 선택할 키들을 가진 원본 객체.
* @param {K[] | readonly K[]} keys - 선택할 키들의 배열.
* @returns {Pick<T, K>} - 지정된 키들만 포함된 새로운 객체.
*
* @example
* const obj = { a: 1, b: 2, c: 3 };
* const result = pick(obj, ['a', 'c']);
* // { a: 1, c: 3 }
*
* @example
* const user = { name: 'John', age: 30, role: 'admin' };
* const result = pick(user, ['name', 'role']);
* // { name: 'John', role: 'admin' }
*/
export function pick<T extends Record<PropertyKey, any>, K extends keyof T>(
obj: T,
keys: K[] | readonly K[]
): Pick<T, K> {
const result = {} as T;
const deepCopiedObj = deepCopy(obj);
const wrappedInArrayKeys = wrapInArray(keys);
const copiedObj = { ...obj };

for (let i = 0; i < keys.length; i++) {
const key = keys[i];

for (const key of wrappedInArrayKeys) {
if (hasProperty(deepCopiedObj, key)) {
result[key] = deepCopiedObj[key];
}
result[key] = copiedObj[key];
}

return result;
Expand Down
13 changes: 13 additions & 0 deletions packages/utils/src/object/pick/pick.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { bench, describe } from 'vitest';
import { pick } from '.';
import { pick as pickLodash } from 'lodash-es';

describe('pick', () => {
bench('@modern-kit/pick', () => {
pick({ a: 1, b: 2, c: 3, d: 4, e: 5 }, ['a', 'c', 'e']);
});

bench('lodash/pick', () => {
pickLodash({ a: 1, b: 2, c: 3, d: 4, e: 5 }, ['a', 'c', 'e']);
});
});
27 changes: 6 additions & 21 deletions packages/utils/src/object/pick/pick.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, expectTypeOf } from 'vitest';
import { pick } from '.';

describe('pick', () => {
it('should return a new object with a single key extracted from an object', () => {
const symbol = Symbol('d');
const inputObj = { a: 1, b: 2, c: 3, [symbol]: 4 } as const;
const pickedObj = pick(inputObj, 'b');

expect(pickedObj).toEqual({ b: 2 });
});

it('should return a new object with multiple keys extracted from an object', () => {
const symbol = Symbol('d');
const inputObj = { a: 1, b: 2, c: 3, [symbol]: 4 } as const;
const pickedObj = pick(inputObj, ['a', 'c']);

expect(pickedObj).toEqual({ a: 1, c: 3 });
});
const inputObj = { a: 1, b: 2, c: 3 };
const pickedObj = pick(inputObj, ['b', 'c']);

it('should return a new object that is deeply copied', () => {
const symbol = Symbol('d');
const inputObj = { a: 1, b: { x: 2, y: 3 }, c: 4, [symbol]: 4 } as const;
const pickedObj = pick(inputObj, ['b']);
expect(pickedObj).toEqual({ b: 2, c: 3 });

expect(pickedObj.b).not.toBe(inputObj.b);
expect(pickedObj.b).toEqual(inputObj.b);
// type
expectTypeOf(pickedObj).toEqualTypeOf<{ b: number; c: number }>();
});
});

0 comments on commit 2adc3d3

Please sign in to comment.