ABOUT ME

-

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

    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. Permutation
    2. Length of String
    3. Flatten
    4. Append to Object
    5. Absolute
    6. String To Union
    7. Merge

     

     

    📘 문제 및 풀이

     

    1. Permutation

    Implement permutation type that transforms union types into the array that includes permutations of unions.
    type perm = Permutation<'A' | 'B' | 'C'>;
    // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Permutation<'A'>, ['A']>>,
      Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
      Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
      Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>,
      Expect<Equal<Permutation<never>, []>>,
    ]

     

    🖌 풀이

    type Permutation<T, U = T> = [T] extends [never] ? [] : T extends U ? [T, ...Permutation<Exclude<U, T>>] : never

    먼저, Permutation은 제네릭 인자를 T로 받되, 이를 분기할 때 참조하기 위해 두 번째 인자(U)에 모든 T를 복사해둔다.

     

    마지막 케이스를 대응하기 위해 T에 never가 포함되는 경우 [] 을 반환한다. 단, 이를 판단하기 위해 T, never를 배열로 감싸줘야한다.

    또한, T가 U에 해당하면 재귀적으로 실행해주고 이 때는 T와 현재 T를 제외한 Permutation 배열 타입으로 설정한다.

    끝으로, 실행할 필요가 없으면 never로 제외시켜주면 된다. (풀이 참고링크)

     


    2. Length of String

    Compute the length of a string literal, which behaves like String#length.
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<LengthOfString<''>, 0>>,
      Expect<Equal<LengthOfString<'kumiko'>, 6>>,
      Expect<Equal<LengthOfString<'reina'>, 5>>,
      Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,
    ]

     

    🖌 풀이

    type LengthOfString<
      S extends string,
      T extends string[] = []
    > = S extends `${infer F}${infer R}`
      ? LengthOfString<R, [F, ...T]>
      : T["length"];

    LengthOfString은 제네릭 인자 2가지를 받는다. S는 문자열을, T는 문자열을 하나씩 자른 배열(split) 이라고 생각하면 된다.

    S를 먼저 F와 R로 나누고, 남은 R에 대해서 재귀적으로 LengthOfString을 실행한다. 이 때, T 배열에는 F를 추가해준다.

    마지막으로 끝나서 빈 문자열이 되면 T의 length를 반환하면 된다.

     


    3. Flatten

    In this challenge, you would need to write a type that takes an array and emitted the flatten array type.

    For example:
    type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Flatten<[]>, []>>,
      Expect<Equal<Flatten<[1, 2, 3, 4]>, [1, 2, 3, 4]>>,
      Expect<Equal<Flatten<[1, [2]]>, [1, 2]>>,
      Expect<Equal<Flatten<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, 5]>>,
      Expect<Equal<Flatten<[{ foo: 'bar'; 2: 10 }, 'foobar']>, [{ foo: 'bar'; 2: 10 }, 'foobar']>>,
    ]

     

    🖌 풀이

    type Flatten<T extends any[], A extends any[] = []> = 
      T extends [infer F, ...infer R] 
        ? F extends any[] 
          ? Flatten<[...F, ...R], A> 
          : Flatten<R, [...A, F]>
        : A

    Flatten은 제네릭 인자로 T(원본배열), A(flat하는 배열) 2가지를 받는다.

    T를 F(첫 번째 인자)와 R(나머지 인자들) 로 나눈 뒤, T에 배열이 남아있으면 Flatten을 재귀적으로, 아니면 A를 반환한다.

     

    단, Flatten을 재귀할 때, 케이스 3, 4처럼 다중 배열인 경우가 있다.

    F가 배열이면 원본에서 첫 번째 인자인 F를 한 번 flat해서 현재 F에 대해 다시 실행시키고,

    F가 배열이 아닌 요소면 첫 번째 인자는 나머지 R들을, 두 번째 인자인 A에 F를 추가해준다.

     


    4. Append to Object

    Implement a type that adds a new field to the interface. The type takes the three arguments. The output should be an object with the new field.

    For example
    type Test = { id: '1' }
    type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type test1 = {
      key: 'cat'
      value: 'green'
    }
    
    type testExpect1 = {
      key: 'cat'
      value: 'green'
      home: boolean
    }
    
    type test2 = {
      key: 'dog' | undefined
      value: 'white'
      sun: true
    }
    
    type testExpect2 = {
      key: 'dog' | undefined
      value: 'white'
      sun: true
      home: 1
    }
    
    type test3 = {
      key: 'cow'
      value: 'yellow'
      sun: false
    }
    
    type testExpect3 = {
      key: 'cow'
      value: 'yellow'
      sun: false
      isMotherRussia: false | undefined
    }
    
    type cases = [
      Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>,
      Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>,
      Expect<Equal<AppendToObject<test3, 'isMotherRussia', false | undefined>, testExpect3>>,
    ]

     

    🖌 풀이

    type AppendToObject<T, U extends string, V> = {
      [P in keyof T | U]: P extends keyof T ? T[P] : V
    }

    AppendToObject 의 제네릭 인자는 3개로, T(추가 전 객체), U(추가할 key, string으로 특정), V(추가할 value) 이다.

    새로운 객체의 키인 P는 keyof T와 U 중 하나에 해당될 것이고, 그 값은 P가 T의 key인 경우 T[P]를 아니면 새로운 V를 넣는다.

     


    5. Absolute

    Implement the Absolute type. A type that take string, number or bigint. The output should be a positive number string

    For example
    type Test = -100;
    type Result = Absolute<Test>; // expected to be "100"
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Absolute<0>, '0'>>,
      Expect<Equal<Absolute<-0>, '0'>>,
      Expect<Equal<Absolute<10>, '10'>>,
      Expect<Equal<Absolute<-5>, '5'>>,
      Expect<Equal<Absolute<'0'>, '0'>>,
      Expect<Equal<Absolute<'-0'>, '0'>>,
      Expect<Equal<Absolute<'10'>, '10'>>,
      Expect<Equal<Absolute<'-5'>, '5'>>,
      Expect<Equal<Absolute<-1_000_000n>, '1000000'>>,
      Expect<Equal<Absolute<9_999n>, '9999'>>,
    ]

     

    🖌 풀이

    type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer N}` ? N : `${T}`

    abs() 와 같은 유틸리티가 있을 줄 알았는데 생각보다 심플한 문제였다.

    T를 문자열화 한 뒤, 맨 앞에 '-' 마이너스가 있으면 이를 뺀 부분을, 아니면 문자열한 T를 반환한다.

     


    6. String to Union

    Implement the String to Union type. Type take string argument. The output should be a union of input letters

    For example
    type Test = '123';
    type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<StringToUnion<''>, never>>,
      Expect<Equal<StringToUnion<'t'>, 't'>>,
      Expect<Equal<StringToUnion<'hello'>, 'h' | 'e' | 'l' | 'l' | 'o'>>,
      Expect<Equal<StringToUnion<'coronavirus'>, 'c' | 'o' | 'r' | 'o' | 'n' | 'a' | 'v' | 'i' | 'r' | 'u' | 's'>>,
    ]

     

    🖌 풀이

    type StringToUnion<T extends string> = T extends `${infer F}${infer R}` ? F | StringToUnion<R> : never;

    T의 모든 문자를 split() 한 유니온 타입을 만드는 문제다.

    문자열이 있다면 앞의 F 그리고 뒤의 R은 재귀적으로 StringToUnion을 적용한 유니온을 반환한다.

     


    7. Merge

    Merge two types into a new type. Keys of the second type overrides keys of the first type.

    For example
    type foo = {
      name: string;
      age: string;
    }
    type coo = {
      age: number;
      sex: string
    }
    
    type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type Foo = {
      a: number
      b: string
    }
    type Bar = {
      b: number
      c: boolean
    }
    
    type cases = [
      Expect<Equal<Merge<Foo, Bar>, {
        a: number
        b: number
        c: boolean
      }>>,
    ]

     

    🖌 풀이

    type Merge<F extends object, S extends object> = {
      [P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P] : never
    };

    마찬가지로 어려울 듯 했으나, 삼항연산을 두 번 활용하면 쉽게 풀 수 있는 문제였다.

     

    반응형
Designed by Tistory.