ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 난이도 Medium - (9)
    Front-End(Web)/Typescript 2023. 2. 28. 04:03
    반응형

    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. Trim Right
    2. Without
    3. Trunc
    4. IndexOf
    5. Join
    6. LastIndexOf
    7. Unique
    8. MapTypes

     

     

    📘 문제 및 풀이

     

    1. Trim Right

    Implement TrimRight<T> which takes an exact string type and returns a new string with the whitespace ending removed.

    For example:
    type Trimed = TrimRight<'   Hello World    '> // expected to be '   Hello World'
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<TrimRight<'str'>, 'str'>>,
      Expect<Equal<TrimRight<'str '>, 'str'>>,
      Expect<Equal<TrimRight<'str     '>, 'str'>>,
      Expect<Equal<TrimRight<'     str     '>, '     str'>>,
      Expect<Equal<TrimRight<'   foo bar  \n\t '>, '   foo bar'>>,
      Expect<Equal<TrimRight<''>, ''>>,
      Expect<Equal<TrimRight<'\n\t '>, ''>>,
    ]

     

    🖌 풀이

    type W = ' ' | '\n' | '\t'
    
    type TrimRight<S extends string> = S extends `${infer Rest}${W}`
      ? TrimRight<Rest>
      : S

    아래 풀이의, 2. Trim, 3. Trim Left 를 응용한 풀이이다. 우측부터 트리밍을 적용해주면 된다. (링크)

     

    [Type-challenges] 난이도 Medium - (2)

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

    abangpa1ace.tistory.com

     


    2. Without

    Implement the type version of Lodash.without, Without<T, U> takes an Array T, number or array U and returns an Array without the elements of U.
    type Res = Without<[1, 2], 1>; // expected to be [2]
    type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
    type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Without<[1, 2], 1>, [2]>>,
      Expect<Equal<Without<[1, 2, 4, 1, 5], [1, 2]>, [4, 5]>>,
      Expect<Equal<Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>, []>>,
    ]

     

    🖌 풀이

    type ToUnion<T> = T extends any[] ? T[number] : T
    
    type Without<T, U extends number | number[]> = T extends [infer F, ...infer R] ?
      F extends ToUnion<U> ? [...Without<R, U>] : [F, ...Without<R, U>]
      : []

    T 배열에 대해 재귀적으로 이어가면 쉽게 풀 수 있다.

    단, 케이스1과 케이스2&3 처럼 U가 숫자 혹은 숫자배열로 구분되므로 이를 유니온으로 만들어주는 ToUnion 유틸리티를 활용한다.

     


    3. Trunc

    Implement the type version of Math.trunc, which takes string or number and returns the integer part of a number by removing any fractional digits.

    For example:
    type A = Trunc<12.34> // 12
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Trunc<0.1>, '0'>>,
      Expect<Equal<Trunc<1.234>, '1'>>,
      Expect<Equal<Trunc<12.345>, '12'>>,
      Expect<Equal<Trunc<-5.1>, '-5'>>,
      Expect<Equal<Trunc<'1.234'>, '1'>>,
      Expect<Equal<Trunc<'-10.234'>, '-10'>>,
      Expect<Equal<Trunc<10>, '10'>>,
    ]

     

    🖌 풀이

    type Trunc<T extends number | string> = `${T}` extends `${infer I}.${infer R}` ? I : `${T}`

    마찬가지로, 소수점을 기준으로 분기하면 쉽게 풀 수 있다.

     


    4. IndexOf

    Implement the type version of Array.indexOf, indexOf<T, U> takes an Array T, any U and returns the index of the first U in Array T.
    type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
    type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // expected to be 2
    type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<IndexOf<[1, 2, 3], 2>, 1>>,
      Expect<Equal<IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 2>>,
      Expect<Equal<IndexOf<[0, 0, 0], 2>, -1>>,
      Expect<Equal<IndexOf<[string, 1, number, 'a'], number>, 2>>,
      Expect<Equal<IndexOf<[string, 1, number, 'a', any], any>, 4>>,
      Expect<Equal<IndexOf<[string, 'a'], 'a'>, 1>>,
      Expect<Equal<IndexOf<[any, 1], 1>, 1>>,
    ]

     

    🖌 풀이

    type IndexOf<T, U, L extends unknown[] = []> = T extends [infer F, ...infer R] ?
      Equal<F, U> extends true ? L['length'] : IndexOf<R, U, [...L, F]>
    : -1

    L에 기존에 비교한 값들(F)을 누적해간다. 현재값(F)가 U와 일치한다면 쌓아온 L배열의 길이가 곧 현재 F의 인덱스가 된다.

    일치를 비교할 때 extends는 상호 포함관계가 허용되므로, Equal 유틸리티를 활용해준다.

     


    5. Join

    Implement the type version of Array.join, Join<T, U> takes an Array T, string or number U and returns the Array T with U stitching up.

    type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e'
    type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World'
    type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212'
    type Res3 = Join<["o"], "u">; // expected to be 'o'
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Join<['a', 'p', 'p', 'l', 'e'], '-'>, 'a-p-p-l-e'>>,
      Expect<Equal<Join<['Hello', 'World'], ' '>, 'Hello World'>>,
      Expect<Equal<Join<['2', '2', '2'], 1>, '21212'>>,
      Expect<Equal<Join<['o'], 'u'>, 'o'>>,
    ]

     

    🖌 풀이

    type Join<T, U extends string | number> = T extends [infer F extends string, ...infer R] ?
      R extends [] ? `${F}` : `${F}${U}${Join<R, U>}`
    : ''

    마찬가지로 T 배열의 전개와 Join의 재귀로 풀 수 있다.

    단, 마지막 요소인 경우(R이 빈 배열)join 문자열(U)를 붙이면 안되므로 분기처리를 해준다.

     


    6. LastIndexOf

    Implement the type version of Array.lastIndexOf, LastIndexOf<T, U> takes an Array T, any U and returns the index of the last U in Array T

    For example:

    type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
    type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>,
      Expect<Equal<LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>,
      Expect<Equal<LastIndexOf<[0, 0, 0], 2>, -1>>,
      Expect<Equal<LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>,
      Expect<Equal<LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>,
    ]

     

    🖌 풀이

    type LastIndexOf<T, U> = T extends [...infer R, infer E] ?
      Equal<E, U> extends true ? R['length'] : LastIndexOf<R, U>
    : -1

    4. IndexOf 의 풀이를 응용한다고 하지만 방법이 많이 다르다.

    맨 끝 인자(E)를 U와 비교하는데, 같다면 R배열의 길이가 E의 인덱스가 되므로 이를, 아니면 남은 R에 대해 재귀한다.

     


    7. Unique

    Implement the type version of Lodash.uniq, Unique takes an Array T, returns the Array T without repeated values.

    type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
    type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
    type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
    type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
    type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Unique<[1, 1, 2, 2, 3, 3]>, [1, 2, 3]>>,
      Expect<Equal<Unique<[1, 2, 3, 4, 4, 5, 6, 7]>, [1, 2, 3, 4, 5, 6, 7]>>,
      Expect<Equal<Unique<[1, 'a', 2, 'b', 2, 'a']>, [1, 'a', 2, 'b']>>,
      Expect<Equal<Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]>, [string, number, 1, 'a', 2, 'b']>>,
      Expect<Equal<Unique<[unknown, unknown, any, any, never, never]>, [unknown, any, never]>>,
    ]

     

    🖌 풀이

    type Include<T extends any[], U> = T extends [infer F, ...infer O]
      ? Equal<F, U> extends true
        ? true
        : Include<O, U>
      : false
    
    type Unique<T extends any[], R extends any[] = []> = T extends []
      ? R
      : T extends [infer F, ...infer O]
        ? Include<R, F> extends true
          ? Unique<O, R>
          : Unique<O, [...R, F]>
        : R

    T 배열을 순회하면서 인자를 비교하는데, [number]로 접근해서 비교하면 유니온이 되므로 각각 비교를 위한 Include 유틸리티를 먼저 만든다.

     

    R은 유니크하게 구분된 값들의 배열로, 이 배열에 F가 포함되는지를 Include 유틸리티로 판단한다.

    있다면 R만, 없다면 F를 포함한 새로운 배열로 Unique를 재귀하며, 모든 순회가 종료되면 R 배열을 반환한다.

     


    8. MapTypes

    Implement MapTypes<T, R> which will transform types in object T to different types defined by type R which has the following structure

    type StringToNumber = {
      mapFrom: string; // value of key which value is string
      mapTo: number; // will be transformed for number
    }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<MapTypes<{ stringToArray: string }, { mapFrom: string; mapTo: [] }>, { stringToArray: [] }>>,
      Expect<Equal<MapTypes<{ stringToNumber: string }, { mapFrom: string; mapTo: number }>, { stringToNumber: number }>>,
      Expect<Equal<MapTypes<{ stringToNumber: string; skipParsingMe: boolean }, { mapFrom: string; mapTo: number }>, { stringToNumber: number; skipParsingMe: boolean }>>,
      Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date } | { mapFrom: string; mapTo: null }>, { date: null | Date }>>,
      Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date | null }>, { date: null | Date }>>,
      Expect<Equal<MapTypes<{ fields: Record<string, boolean> }, { mapFrom: Record<string, boolean>; mapTo: string[] }>, { fields: string[] }>>,
      Expect<Equal<MapTypes<{ name: string }, { mapFrom: boolean; mapTo: never }>, { name: string }>>,
      Expect<Equal<MapTypes<{ name: string; date: Date }, { mapFrom: string; mapTo: boolean } | { mapFrom: Date; mapTo: string }>, { name: boolean; date: string }>>,
    ]

     

    🖌 풀이

    type MapTypes<T, R extends { mapFrom: any; mapTo: any; }> = {
      [P in keyof T]: T[P] extends R['mapFrom'] ? Extract<R, { mapFrom: T[P] }>['mapTo'] : T[P];
    }

    우선 R을 { mapFrom, mapTo } 객체를 extends 하면 유니온 형태로 인식이 된다.

    T[P]가 R['mapFrom']에 해당되지 않으면 T[P] 자체를, 해당되면 R에서 mapFrom이 T[P]에 해당하는 값만 추출한 mapTo를 반영한다.

    반응형
Designed by Tistory.