ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 난이도 Medium - (11)
    Front-End(Web)/Typescript 2023. 3. 3. 04:00
    반응형

    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. GetMiddleElement
    2. Integer
    3. ToPrimitive
    4. DeepMutable
    5. All
    6. Filter

     

     

    📘 문제 및 풀이

     

    1. GetMiddleElement

    Get the middle element of the array by implementing a GetMiddleElement method, represented by an array

    If the length of the array is odd, return the middle element If the length of the array is even, return the middle two elements

    For example
    type simple1 = GetMiddleElement<[1, 2, 3, 4, 5]>, // expected to be [3]
    type simple2 = GetMiddleElement<[1, 2, 3, 4, 5, 6]> // expected to be [3, 4]
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<GetMiddleElement<[]>, []>>,
      Expect<Equal<GetMiddleElement<[1, 2, 3, 4, 5]>, [3]>>,
      Expect<Equal<GetMiddleElement<[1, 2, 3, 4, 5, 6]>, [3, 4]>>,
      Expect<Equal<GetMiddleElement<[() => string]>, [() => string]>>,
      Expect<Equal<GetMiddleElement<[() => number, '3', [3, 4], 5]>, ['3', [3, 4]]>>,
      Expect<Equal<GetMiddleElement<[() => string, () => number]>, [() => string, () => number]>>,
      Expect<Equal<GetMiddleElement<[never]>, [never]>>,
    ]
    // @ts-expect-error
    type error = GetMiddleElement<1, 2, 3>

     

    🖌 풀이

    type GetMiddleElement<T extends readonly unknown[]> = T['length'] extends 0 | 1 | 2
      ? T
      : T extends [unknown, ...infer Middle, unknown]
      ? GetMiddleElement<Middle>
      : never

    T의 길이가 0, 1, 2 중 하나면 중간요소가 맞으므로 이를 반환하면 된다.

    아니라면, 양 옆을 unknown 요소로 제거한 가운데(Middle) 배열에 유틸리티를 재귀한다.

     

     


    2. Integer

    Please complete type Integer<T>, type T inherits from number, if T is an integer return it, otherwise return never.
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    import { ExpectFalse, NotEqual } from '@type-challenges/utils'
    
    let x = 1
    let y = 1 as const
    
    type cases1 = [
      Expect<Equal<Integer<1>, 1>>,
      Expect<Equal<Integer<1.1>, never>>,
      Expect<Equal<Integer<1.0>, 1>>,
      Expect<Equal<Integer<typeof x>, never>>,
      Expect<Equal<Integer<typeof y>, 1>>,
    ]

     

    🖌 풀이

    type Integer<T extends number> = number extends T
      ? never
      : `${T}` extends `${string}.${string}`
        ? never
        : T

    아래의 삼항연산 코드로도 구분이 가능하다. T(숫자)를 문자화한 값에 소수점이 있는지 여부로 판단하는 것이다.

    단, 케이스4 처럼 typeof의 경우를 필터링하기 위해 T에 number 타입으로 넘어왔는지 여부를 추가로 분기해준다.

     


    3. ToPrimitive

    Convert a property of type literal (label type) to a primitive type.

    For example
    type X = {
      name: 'Tom',
      age: 30,
      married: false,
      addr: {
        home: '123456',
        phone: '13111111111'
      }
    }
    
    type Expected = {
      name: string,
      age: number,
      married: boolean,
      addr: {
        home: string,
        phone: string
      }
    }
    type Todo = ToPrimitive<X> // should be same as `Expected`
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type PersonInfo = {
      name: 'Tom'
      age: 30
      married: false
      addr: {
        home: '123456'
        phone: '13111111111'
      }
      hobbies: ['sing', 'dance']
    }
    
    type ExpectedResult = {
      name: string
      age: number
      married: boolean
      addr: {
        home: string
        phone: string
      }
      hobbies: [string, string]
    }
    
    type cases = [
      Expect<Equal<ToPrimitive<PersonInfo>, ExpectedResult>>,
    ]

     

    🖌 풀이

    type ToPrimitive<T> = {
      [P in keyof T]: T[P] extends string
        ? string
        : T[P] extends number
        ? number
        : T[P] extends boolean
        ? boolean
        : ToPrimitive<T[P]>;
    };

    원시타입(string, number, boolean, bigint, symbol) 이라면 그대로 반환해주고, 아니라면 유틸리티를 재귀해준다.

    객체뿐만 아니라 배열도 [P in keyof T] 로 접근이 가능한 것이다.

     


    4. DeepMutable

    Implement a generic DeepMutable which make every parameter of an object - and its sub-objects recursively - mutable.

    For example

    type X = {
      readonly a: () => 1
      readonly b: string
      readonly c: {
        readonly d: boolean
        readonly e: {
          readonly g: {
            readonly h: {
              readonly i: true
              readonly j: "s"
            }
            readonly k: "hello"
          }
        }
      }
    }
    
    type Expected = {
      a: () => 1
      b: string
      c: {
        d: boolean
        e: {
          g: {
            h: {
              i: true
              j: "s"
            }
            k: "hello"
          }
        }
      }
    }
    
    type Todo = DeepMutable<X> // should be same as `Expected`

    You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on do not need to be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.

     

    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Test1 {
      readonly title: string
      readonly description: string
      readonly completed: boolean
      readonly meta: {
        readonly author: string
      }
    }
    type Test2 = {
      readonly a: () => 1
      readonly b: string
      readonly c: {
        readonly d: boolean
        readonly e: {
          readonly g: {
            readonly h: {
              readonly i: true
              readonly j: 's'
            }
            readonly k: 'hello'
          }
          readonly l: readonly [
            'hi',
            {
              readonly m: readonly ['hey']
            },
          ]
        }
      }
    }
    interface DeepMutableTest1 {
      title: string
      description: string
      completed: boolean
      meta: {
        author: string
      }
    }
    
    type DeepMutableTest2 = {
      a: () => 1
      b: string
      c: {
        d: boolean
        e: {
          g: {
            h: {
              i: true
              j: 's'
            }
            k: 'hello'
          }
          l: [
            'hi',
            {
              m: ['hey']
            },
          ]
        }
      }
    }
    
    type cases = [
      Expect<Equal<DeepMutable<Test1>, DeepMutableTest1>>,
      Expect<Equal<DeepMutable<Test2>, DeepMutableTest2>>,
    ]
    
    type errors = [
      // @ts-expect-error
      DeepMutable<'string'>,
      // @ts-expect-error
      DeepMutable<0>,
    ]

     

    🖌 풀이

    type DeepMutable<T extends object> = {
      -readonly [P in keyof T]: T[P] extends object ? 
        T[P] extends (...args: unknown[]) => any ? T[P] : DeepMutable<T[P]>
      : T[P]
    }

    Mapped 타입에서 Mapping 연산자(-)로 readonly를 재귀적으로 제거해주면 된다.

    케이스2 를 참고하면 함수도 object여서 에러가 발생하므로, 이에 대해서만 예외처리를 해주면 된다.

     


    5. All

    Returns true if all elements of the list are equal to the second parameter passed in, false if there are any mismatches.

    For example

    type Test1 = [1, 1, 1]
    type Test2 = [1, 1, 2]
    
    type Todo = All<Test1, 1> // should be same as true
    type Todo2 = All<Test2, 1> // should be same as false
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<All<[1, 1, 1], 1>, true>>,
      Expect<Equal<All<[1, 1, 2], 1>, false>>,
      Expect<Equal<All<['1', '1', '1'], '1'>, true>>,
      Expect<Equal<All<['1', '1', '1'], 1>, false>>,
      Expect<Equal<All<[number, number, number], number>, true>>,
      Expect<Equal<All<[number, number, string], number>, false>>,
      Expect<Equal<All<[null, null, null], null>, true>>,
      Expect<Equal<All<[[1], [1], [1]], [1]>, true>>,
      Expect<Equal<All<[{}, {}, {}], {}>, true>>,
      Expect<Equal<All<[never], never>, true>>,
      Expect<Equal<All<[any], any>, true>>,
      Expect<Equal<All<[unknown], unknown>, true>>,
      Expect<Equal<All<[any], unknown>, false>>,
      Expect<Equal<All<[unknown], any>, false>>,
    ]

     

    🖌 풀이

    type All<T extends unknown[], U> = Equal<T[number], U> extends true ? true : false

    T[number] 로 유니온으로 접근할 수 있다는 특성을 활용하면 쉽게 풀 수 있다.

     


    6. Filter

    Implement the type Filter<T, Predicate> takes an Array T, primitive type or union primitive type Predicate and returns an Array include the elements of Predicate.

    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type Falsy = false | 0 | '' | null | undefined
    
    type cases = [
      Expect<Equal<Filter<[0, 1, 2], 2>, [2]>>,
      Expect<Equal<Filter<[0, 1, 2], 0 | 1>, [0, 1]>>,
      Expect<Equal<Filter<[0, 1, 2], Falsy>, [0]>>,
    ]

     

    🖌 풀이

    type Filter<T extends any[], P, A extends any[] = []> = T extends [infer F, ...infer R] ?
      Filter<R, P, (F extends P ? [...A, F] : A)>
    : A

    배열을 전개해서 풀 수 있는 문제였다.

    반응형
Designed by Tistory.