-
[Type-challenges] 난이도 Medium - (7)Front-End(Web)/Typescript 2023. 2. 27. 17:51반응형
Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)
https://github.com/type-challenges/type-challenges
📘 목차 - Medium
- Reverse
- Flip Arguments
- FlattenDepth
- BEM style string
- InorderTraversal
- Flip
📘 문제 및 풀이
1. Reverse
Implement the type version of Array.reverse
For example:type a = Reverse<['a', 'b']> // ['b', 'a'] type b = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Reverse<[]>, []>>, Expect<Equal<Reverse<['a', 'b']>, ['b', 'a']>>, Expect<Equal<Reverse<['a', 'b', 'c']>, ['c', 'b', 'a']>>, ] type errors = [ // @ts-expect-error Reverse<'string'>, // @ts-expect-error Reverse<{ key: 'value' }>, ]
🖌 풀이
type Reverse<T extends any[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : []
배열 전개를 재귀적으로 사용하면 간단히 reverse가 가능하다.
2. Flip Arguments
Implement the type version of lodash's _.flip.
Type FlipArguments<T> requires function type T and returns a new function type which has the same return type of T but reversed parameters.
For example:type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void> // (arg0: boolean, arg1: number, arg2: string) => void
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FlipArguments<() => boolean>, () => boolean>>, Expect<Equal<FlipArguments<(foo: string) => number>, (foo: string) => number>>, Expect<Equal<FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>, (arg0: boolean, arg1: number, arg2: string) => void>>, ] type errors = [ // @ts-expect-error FlipArguments<'string'>, // @ts-expect-error FlipArguments<{ key: 'value' }>, // @ts-expect-error FlipArguments<['apple', 'banana', 100, { a: 1 }]>, // @ts-expect-error FlipArguments<null | undefined>, ]
🖌 풀이
type Reverse<T extends any[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : [] type FlipArguments<T extends (...args: any) => any> = (...args: Reverse<Parameters<T>>) => ReturnType<T>;
위 1번의 Reverse를 활용하면 쉽게 풀 수 있다. 함수 매개변수 타입(Parameters)에 Reverse만 적용해주면 된다.
3. FlattenDepth
Recursively flatten array up to depth times.
For example:type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
If the depth is provided, it's guaranteed to be positive integer.
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FlattenDepth<[]>, []>>, Expect<Equal<FlattenDepth<[1, 2, 3, 4]>, [1, 2, 3, 4]>>, Expect<Equal<FlattenDepth<[1, [2]]>, [1, 2]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>, [1, 2, 3, 4, [5]]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, [[5]]]>>, Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 3>, [1, 2, 3, 4, [5]]>>, Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 19260817>, [1, 2, 3, 4, 5]>>, ]
🖌 풀이
type FlattenOnce<T extends any[]> = T extends [infer F, ...infer R]? [...F extends [...infer K]? K : [F], ...FlattenOnce<R> ] : T type FlattenDepth<T extends any[], C extends number = 1, P extends any[] = []> = P['length'] extends C ? T : T extends FlattenOnce<T>? T: FlattenDepth<FlattenOnce<T>, C, [...P, any] >
먼저, FlattenOnce는 flatten을 한 번 실행하는 유틸리티이다.
그리고, FlattenDepth를 설정한다. C는 flatten 횟수, P는 flatten 한 횟수를 any의 개수로 저장하는 배열이다.
P의 길이(length)와 C를 비교하거나, T가 flatten한 T와 같으면 더 이상 진행이 의미없으므로 이 때 T를 반환한다.
아니라면, FlattenDepth를 재귀적으로 실행해주며, T는 한 번 flatten 해주고 배열엔 임의 요소를 하나 추가해준다. (참고링크)
4. BEM style string
The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.
For example, the block component would be represented as btn, element that depends upon the block would be represented as btn__price, modifier that changes the style of the block would be represented as btn--big or btn__price--warning.
Implement BEM<B, E, M> which generate string union from these three parameters. Where B is a string
literal, E and M are string arrays (can be empty)./* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<BEM<'btn', ['price'], []>, 'btn__price'>>, Expect<Equal<BEM<'btn', ['price'], ['warning', 'success']>, 'btn__price--warning' | 'btn__price--success' >>, Expect<Equal<BEM<'btn', [], ['small', 'medium', 'large']>, 'btn--small' | 'btn--medium' | 'btn--large' >>, ]
🖌 풀이
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`
배열[number] 로 각 엘리먼트에 유니온처럼 접근할 수 있는 방법을 응용하였다.
E, M 각 배열이 빈 경우는 빈 문자열('')을, 문자열 값들이 존재하는 경우는 [number]로 각 엘리먼트에 접근하여 문법처럼 적용해준다.
5. InorderTraversal
Implement the type version of binary tree inorder traversal.
For example:const tree1 = { val: 1, left: null, right: { val: 2, left: { val: 3, left: null, right: null, }, right: null, }, } as const type A = InorderTraversal<typeof tree1> // [1, 3, 2]
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' const tree1 = { val: 1, left: null, right: { val: 2, left: { val: 3, left: null, right: null, }, right: null, }, } as const const tree2 = { val: 1, left: null, right: null, } as const const tree3 = { val: 1, left: { val: 2, left: null, right: null, }, right: null, } as const const tree4 = { val: 1, left: null, right: { val: 2, left: null, right: null, }, } as const type cases = [ Expect<Equal<InorderTraversal<null>, []>>, Expect<Equal<InorderTraversal<typeof tree1>, [1, 3, 2]>>, Expect<Equal<InorderTraversal<typeof tree2>, [1]>>, Expect<Equal<InorderTraversal<typeof tree3>, [2, 1]>>, Expect<Equal<InorderTraversal<typeof tree4>, [1, 2]>>, ]
🖌 풀이
interface TreeNode { val: number left: TreeNode | null right: TreeNode | null } type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode] ? ( [ ...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']> ] ) : []
InorderTraversal은 트리 노드 값들을 배열로 펼치는 유틸리티로, 현재 인자 기준으로 left값은 바로 왼쪽, right값은 바로 오른쪽에 위치해야한다.
T가 TreeNode에 해당하면 좌, 우에는 InorderTraversal을 재귀적으로 적용해주고 가운데에는 현재값을 넣어준다.
단, 이 때 제네릭 인자 T가 유니온이므로, Distribute Conditional Types(분배 조건부 타입)을 회피하기 위해 []로 감싸준다. (참고링크)
6. Flip
Implement the type of just-flip-object. Examples:
Flip<{ a: "x", b: "y", c: "z" }>; // {x: 'a', y: 'b', z: 'c'} Flip<{ a: 1, b: 2, c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'} Flip<{ a: false, b: true }>; // {false: 'a', true: 'b'}
No need to support nested objects and values which cannot be object keys such as arrays
/* _____________ Test Cases _____________ */ import type { Equal, Expect, NotEqual } from '@type-challenges/utils' type cases = [ Expect<Equal<{ a: 'pi' }, Flip<{ pi: 'a' }>>>, Expect<NotEqual<{ b: 'pi' }, Flip<{ pi: 'a' }>>>, Expect<Equal<{ 3.14: 'pi'; true: 'bool' }, Flip<{ pi: 3.14; bool: true }>>>, Expect<Equal<{ val2: 'prop2'; val: 'prop' }, Flip<{ prop: 'val'; prop2: 'val2' }>>>, ]
🖌 풀이
type Flip<T extends Record<PropertyKey, any>> = { [P in keyof T as T[P] extends PropertyKey ? T[P] : `${T[P]}`]: P }
이전에 풀었던, Mapped Type에서 키값에 value(T[P])를 사용하는 방식을 활용하면 쉽게 풀 수 있다.
단, T[P]가 키값으로 사용 가능한 타입(string, number, symbol) 인지 여부를 확인해서, 아니라면 리터럴화 해준다.
반응형'Front-End(Web) > Typescript' 카테고리의 다른 글
[Type-challenges] 난이도 Medium - (9) (2) 2023.02.28 [Type-challenges] 난이도 Medium - (8) (0) 2023.02.28 [Type-challenges] 난이도 Medium - (6) (0) 2023.02.27 [Type-challenges] 난이도 Medium - (5) (0) 2023.02.23 [Type-challenges] 난이도 Medium - (4) (0) 2023.02.16