🌱 ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 15, 16, 20, 62, 106
    Front-End/TypeScript 2024. 3. 8. 14:23
    🍀 목차
    15 - Last of Array
    16 - Pop
    20 - Promise.all
    62 - Type Lookup
    106 - Trim Left

     

     

    15 - Last of Array

    // 이 챌린지에는 TypeScript 4.0 사용이 권장됩니다.
    // 배열 T를 사용하고 마지막 요소를 반환하는 제네릭 Last<T>를 구현합니다.
    
    // 예시
    type arr1 = ['a', 'b', 'c']
    type arr2 = [3, 2, 1]
    
    type tail1 = Last<arr1> // expected to be 'c'
    type tail2 = Last<arr2> // expected to be 1
    /* _____________ 여기에 코드 입력 _____________ */
    
    type Last<T extends any[]> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Last<[2]>, 2>>,
      Expect<Equal<Last<[3, 2, 1]>, 1>>,
      Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>>,
    ]
    type Last<T extends unknown[]> = T extends [...infer _, infer U] ? U : never;

     

     감명 깊게 본 풀이는, 앞에 unknown(임의의 요소)을 추가하고 T의 length로 마지막 원소를 반환하는 방법이다.

    type Last<T extends unknown[]> = [unknown, ...T][T['length']];

     

     별개로 왜 4.0 사용이 권장되나 궁금해서 Documentation을 보니 4.0 이전 버전에서는 concat을 구현하기 위해 여러 개의 오버로드로 작성했다고 한다.

    // 아래같은 무수한 오버로드를 "천 개의 오버로드로 인한 죽음"이라고 한다... 이름부터 무섭다.
    function concat(arr1: [], arr2: []): [];
    function concat<A>(arr1: [A], arr2: []): [A];
    function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
    function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
    function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
    function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
    function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)
    
    // 포괄적인 케이스?
    function concat<T, U>(arr1: T[], arr2: U[]): Array<T | U>;

     

     튜플을 사용할 때 입력 길이, 요소 순서에 대한 어떤 것도 처리하지 않았다. 4.0 버전에서는 스프레드 연산자에서 제네릭 타입을 사용하여 작동하는 실제 타입을 모르더라도 튜플과 배열에 대한 고차함수를 표현할 수 있게 되었다.

     

    function tail<T extends any[]>(arr: readonly [any, ...T]) {
      const [_ignored, ...rest] = arr;
      return rest;
    }
    const myTuple = [1, 2, 3, 4] as const;
    const myArray = ["hello", "world"];
    const r1 = tail(myTuple);
    //    ^ = const r1: [2, 3, 4]
    const r2 = tail([...myTuple, ...myArray] as const);
    //    ^ = const r2: [2, 3, 4, ...string[]]

     

     대략적으로 구조 분해 할당에 대한 타입 추론이 개선되고 더 많은 유형의 데이터를 다룰 수 있게 되었다고 이해했다.

     

     

    16 - Pop

    // 이 챌린지에는 TypeScript 4.0 사용이 권장됩니다.
    // 배열 T를 사용해 마지막 요소를 제외한 배열을 반환하는 제네릭 Pop<T>를 구현합니다.
    
    type arr1 = ['a', 'b', 'c', 'd']
    type arr2 = [3, 2, 1]
    
    type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
    type re2 = Pop<arr2> // expected to be [3, 2]
    
    // 더보기: 비슷하게 Shift, Push 그리고 Unshift도 구현할 수 있을까요?

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type Pop<T extends any[]> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Pop<[3, 2, 1]>, [3, 2]>>,
      Expect<Equal<Pop<['a', 'b', 'c', 'd']>, ['a', 'b', 'c']>>,
      Expect<Equal<Pop<[]>, []>>,
    ]

     

    type Pop<T extends unknown[]> = T extends [...infer U, unknown]? U : T;
    type Shift<T extends unknown[]> = T extends [unknown, ...infer U]? U : T;
    type Push<T extends unknown[], U>= [...T, U];
    type Unshift<T extends unknown[], U>=[U, ...T];

     

     

    20 - Promise.all

    // Type the function PromiseAll that accepts an array of PromiseLike objects, 
    // the returning value should be Promise<T> where T is the resolved result array.
    
    const promise1 = Promise.resolve(3);
    const promise2 = 42;
    const promise3 = new Promise<string>((resolve, reject) => {
      setTimeout(resolve, 100, 'foo');
    });
    
    // expected to be `Promise<[number, 42, string]>`
    const p = PromiseAll([promise1, promise2, promise3] as const)

     

    /* _____________ Your Code Here _____________ */
    
    declare function PromiseAll(values: any): any
    
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    const promiseAllTest1 = PromiseAll([1, 2, 3] as const)
    const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const)
    const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
    const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3])
    
    type cases = [
      Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>,
      Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>,
      Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>,
      Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>,
    ]

     

    // 유틸리티 타입 Awaited를 사용해도 된다.
    type MyAwaited<T> = T extends Promise<infer U>? U : T;
    
    declare function PromiseAll<T extends unknown[]>(values: readonly [...T]): Promise<{[key in keyof T]: MyAwaited<T[key]>}>;

     

     

    62 - Type Lookup

    // 때때로 유니온 타입의 특정 속성을 기준으로 조회할 수도 있습니다.
    
    // 이 챌린지에서는 유니온 타입 Cat | Dog에서 공통으로 사용하는
    // type 필드를 기준으로 해당하는 타입을 얻고자 합니다. 
    // 다시 말해서, 다음 예시에서는 LookUp<Cat | Dog, 'dog'>으로 Dog 타입을, 
    // LookUp<Cat | Dog, 'cat'>으로 Cat 타입을 얻을 수 있습니다.
    
    // 예시
    interface Cat {
      type: 'cat'
      breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
    }
    
    interface Dog {
      type: 'dog'
      breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
      color: 'brown' | 'white' | 'black'
    }
    
    type MyDogType = LookUp<Cat | Dog, 'dog'> // 기대되는 결과는 `Dog`입니다.

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type LookUp<U, T> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Cat {
      type: 'cat'
      breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
    }
    
    interface Dog {
      type: 'dog'
      breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
      color: 'brown' | 'white' | 'black'
    }
    
    type Animal = Cat | Dog
    
    type cases = [
      Expect<Equal<LookUp<Animal, 'dog'>, Dog>>,
      Expect<Equal<LookUp<Animal, 'cat'>, Cat>>,
    ]

     

    type LookUp<U, T> = U extends {type: T} ? U : never;

     

     Mapped type으로 작성하다 막혀서 참고했다. 

     

    106 - Trim Left

    // 정확한 문자열 타입이고 시작 부분의 공백이 제거된 새 문자열을 반환하는 TrimLeft<T>를 구현하십시오.
    
    // 예시
    type trimed = TrimLeft<'  Hello World  '> // 기대되는 결과는 'Hello World  '입니다.

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type TrimLeft<S extends string> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<TrimLeft<'str'>, 'str'>>,
      Expect<Equal<TrimLeft<' str'>, 'str'>>,
      Expect<Equal<TrimLeft<'     str'>, 'str'>>,
      Expect<Equal<TrimLeft<'     str     '>, 'str     '>>,
      Expect<Equal<TrimLeft<'   \n\t foo bar '>, 'foo bar '>>,
      Expect<Equal<TrimLeft<''>, ''>>,
      Expect<Equal<TrimLeft<' \n\t'>, ''>>,
    ]
    type TrimLeft<S extends string> = S extends `${infer L}${infer R}` ? L extends ' ' | '\n' | '\t' ? TrimLeft<R> : S : S;

     

     후에 답안들을 보며 더 간단하게 고쳤다.

    type Space = ' ' | '\n' | '\t';
    
    type TrimLeft<S extends string> = S extends `${Space}${infer R}` ? TrimLeft<R> : S;

     

     

    참고자료

    https://github.com/type-challenges/type-challenges/blob/main/questions/00015-medium-last/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00016-medium-pop/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00020-medium-promise-all/README.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00062-medium-type-lookup/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00106-medium-trimleft/README.ko.md

    댓글

🍀 Y0ungZ dev blog.
스크롤 버튼