ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 난이도 Medium - (7)
    Front-End(Web)/Typescript 2023. 2. 27. 17:51
    반응형

    Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)

     

    https://github.com/type-challenges/type-challenges

     

    GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

    Collection of TypeScript type challenges with online judge - GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

    github.com

     


    📘 목차 - Medium

    1. Reverse
    2. Flip Arguments
    3. FlattenDepth
    4. BEM style string
    5. InorderTraversal
    6. 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] >

    먼저, FlattenOnceflatten을 한 번 실행하는 유틸리티이다.

     

    그리고, 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) 인지 여부를 확인해서, 아니라면 리터럴화 해준다.

     

    반응형
Designed by Tistory.