ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 난이도 Medium - (5)
    Front-End(Web)/Typescript 2023. 2. 23. 02:18
    반응형

    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. Percentage Parser
    2. Drop Char
    3. MinusOne
    4. PickByType
    5. StartsWith
    6. EndsWith
    7. PartialByKeys

     

     

    📘 문제 및 풀이

     

    1. Percentage Parser

    Implement PercentageParser. According to the /^(\+|\-)?(\d*)?(\%)?$/ regularity to match T and get three matches.
    The structure should be: [plus or minus, number, unit] If it is not captured, the default is an empty string.

    For example:
    type PString1 = ''
    type PString2 = '+85%'
    type PString3 = '-85%'
    type PString4 = '85%'
    type PString5 = '85'
    
    type R1 = PercentageParser<PString1> // expected ['', '', '']
    type R2 = PercentageParser<PString2> // expected ["+", "85", "%"]
    type R3 = PercentageParser<PString3> // expected ["-", "85", "%"]
    type R4 = PercentageParser<PString4> // expected ["", "85", "%"]
    type R5 = PercentageParser<PString5> // expected ["", "85", ""]
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type Case0 = ['', '', '']
    type Case1 = ['+', '', '']
    type Case2 = ['+', '1', '']
    type Case3 = ['+', '100', '']
    type Case4 = ['+', '100', '%']
    type Case5 = ['', '100', '%']
    type Case6 = ['-', '100', '%']
    type Case7 = ['-', '100', '']
    type Case8 = ['-', '1', '']
    type Case9 = ['', '', '%']
    type Case10 = ['', '1', '']
    type Case11 = ['', '100', '']
    
    type cases = [
      Expect<Equal<PercentageParser<''>, Case0>>,
      Expect<Equal<PercentageParser<'+'>, Case1>>,
      Expect<Equal<PercentageParser<'+1'>, Case2>>,
      Expect<Equal<PercentageParser<'+100'>, Case3>>,
      Expect<Equal<PercentageParser<'+100%'>, Case4>>,
      Expect<Equal<PercentageParser<'100%'>, Case5>>,
      Expect<Equal<PercentageParser<'-100%'>, Case6>>,
      Expect<Equal<PercentageParser<'-100'>, Case7>>,
      Expect<Equal<PercentageParser<'-1'>, Case8>>,
      Expect<Equal<PercentageParser<'%'>, Case9>>,
      Expect<Equal<PercentageParser<'1'>, Case10>>,
      Expect<Equal<PercentageParser<'100'>, Case11>>,
    ]

     

    🖌 풀이

    // your answers
    type PercentageParser<T> = T extends `${infer F}${infer R}`
      ? F extends "+" | "-"
        ? [F, ...(R extends `${infer X}%` ? [X, "%"] : [R, ""])]
        : ["", ...(T extends `${infer Y}%` ? [Y, "%"] : [T, ""])]
      : ["", "", ""];

    위 풀이가 생각보다 심플한 해결법을 주었다.

    먼저, 첫 글자인 F가 +/-에 해당하면 이를, 아니면 빈 문자열을 시작으로 배열을 반환한다.

    이후, 전자면 나머지 R을, 후자면 전체 T단언타입과 %에 해당하는지 여부에 따라 분기해주면 된다.

    모든 경우에 해당하지 않으면, 빈 문자열들로 이루어진 배열을 반환한다.

     


    2. Drop Char

    Drop a specified char from a string.

    For example:
    type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      // @ts-expect-error
      Expect<Equal<DropChar<'butter fly!', ''>, 'butterfly!'>>,
      Expect<Equal<DropChar<'butter fly!', ' '>, 'butterfly!'>>,
      Expect<Equal<DropChar<'butter fly!', '!'>, 'butter fly'>>,
      Expect<Equal<DropChar<'    butter fly!        ', ' '>, 'butterfly!'>>,
      Expect<Equal<DropChar<' b u t t e r f l y ! ', ' '>, 'butterfly!'>>,
      Expect<Equal<DropChar<' b u t t e r f l y ! ', 'b'>, '  u t t e r f l y ! '>>,
      Expect<Equal<DropChar<' b u t t e r f l y ! ', 't'>, ' b u   e r f l y ! '>>,
    ]

     

    🖌 풀이

    type DropChar<S, C> = S extends `${infer F}${infer R}`
      ? F extends C
        ? DropChar<R, C>
        : `${F}${DropChar<R, C>}`
      : S

    이전에 계속 풀었던 String 타입을 재귀적으로 푸는 문제이다. 다만, 앞에 F를 붙이면서 안쪽에서 DropChar로 재귀하고 있는 점이 다르다.

     


    3. MinusOne

    Given a number (always positive) as a type. Your type should return the number decreased by one.

    For example:
    type Zero = MinusOne<1> // 0
    type FiftyFour = MinusOne<55> // 54
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<MinusOne<1>, 0>>,
      Expect<Equal<MinusOne<55>, 54>>,
      Expect<Equal<MinusOne<3>, 2>>,
      Expect<Equal<MinusOne<100>, 99>>,
      Expect<Equal<MinusOne<1101>, 1100>>,
      Expect<Equal<MinusOne<0>, -1>>,
      Expect<Equal<MinusOne<9_007_199_254_740_992>, 9_007_199_254_740_991>>,
    ]

     

    🖌 풀이

     

    풀이는 이 링크를 참고하자. 숫자를 뒤집어서 0이 나오면 이를 최대한 줄이고, 그것이 아니면 이것의 -1 한 숫자를 반환한다.

    0~9까지 숫자에 맵핑되는 -1 숫자를 9~0으로 다시 만든다. (다소 노가다성의 풀이다)

     


    4. PickByType

    From T, pick a set of properties whose type are assignable to U.

    For Example
    type OnlyBoolean = PickByType<{
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }, boolean> // { isReadonly: boolean; isEnable: boolean; }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface Model {
      name: string
      count: number
      isReadonly: boolean
      isEnable: boolean
    }
    
    type cases = [
      Expect<Equal<PickByType<Model, boolean>, { isReadonly: boolean; isEnable: boolean }>>,
      Expect<Equal<PickByType<Model, string>, { name: string }>>,
      Expect<Equal<PickByType<Model, number>, { count: number }>>,
    ]

     

    🖌 풀이

    type PickByType<T, U> = {
      [P in keyof T as T[P] extends U ? P : never] : T[P];
    }

    as에서 키 뿐만이 아니라 위처럼 값을 가지고도 분기를 할 수 있다. T[P]가 U에 해당하는 경우가 아니면 키값을 never로 지정한다.

     


    5. StartsWith

    Implement StartsWith<T, U> which takes two exact string types and returns whether T starts with U

    For example
    type a = StartsWith<'abc', 'ac'> // expected to be false
    type b = StartsWith<'abc', 'ab'> // expected to be true
    type c = StartsWith<'abc', 'abcd'> // expected to be false
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<StartsWith<'abc', 'ac'>, false>>,
      Expect<Equal<StartsWith<'abc', 'ab'>, true>>,
      Expect<Equal<StartsWith<'abc', 'abc'>, true>>,
      Expect<Equal<StartsWith<'abc', 'abcd'>, false>>,
      Expect<Equal<StartsWith<'abc', ''>, true>>,
      Expect<Equal<StartsWith<'abc', ' '>, false>>,
      Expect<Equal<StartsWith<'', ''>, true>>,
    ]

     

    🖌 풀이

    type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false;

    재귀로 풀지 않아도 쉽게 풀 수 있는 문제였다. T가 U와 string을 합친 리터럴에 해당하는지 여부를 반환한다.

     


    6. EndsWith

    Implement EndsWith<T, U> which takes two exact string types and returns whether T ends with U

    For example:
    type a = EndsWith<'abc', 'bc'> // expected to be true
    type b = EndsWith<'abc', 'abc'> // expected to be true
    type c = EndsWith<'abc', 'd'> // expected to be false
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<EndsWith<'abc', 'bc'>, true>>,
      Expect<Equal<EndsWith<'abc', 'abc'>, true>>,
      Expect<Equal<EndsWith<'abc', 'd'>, false>>,
      Expect<Equal<EndsWith<'abc', 'ac'>, false>>,
      Expect<Equal<EndsWith<'abc', ''>, true>>,
      Expect<Equal<EndsWith<'abc', ' '>, false>>,
    ]

     

    🖌 풀이

    type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false

    위의 StartsWith 문제를 반대로 풀어주면 됬다.

     


    7. PartialByKeys

    Implement a generic PartialByKeys<T, K> which takes two type argument T and K.
    K specify the set of properties of T that should set to be optional. When K is not provided, it should make all properties optional just like the normal Partial<T>.

    For example
    interface User {
      name: string
      age: number
      address: string
    }
    
    type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
    /* _____________ Test Cases _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    interface User {
      name: string
      age: number
      address: string
    }
    
    interface UserPartialName {
      name?: string
      age: number
      address: string
    }
    
    interface UserPartialNameAndAge {
      name?: string
      age?: number
      address: string
    }
    
    type cases = [
      Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>,
      Expect<Equal<PartialByKeys<User, 'name' | 'age'>, UserPartialNameAndAge>>,
      Expect<Equal<PartialByKeys<User>, Partial<User>>>,
      // @ts-expect-error
      Expect<Equal<PartialByKeys<User, 'name' | 'unknown'>, UserPartialName>>,
    ]

     

    🖌 풀이

    type Merge<T> = {
      [K in keyof T]: T[K];
    }
    
    type PartialByKeys<T, K extends keyof T = keyof T> = Merge<{
      [P in keyof T as P extends K ? P : never]?: T[P];
    } & {
      [P in keyof T as P extends K ? never : P]: T[P];
    }>;

    풀이는 예상한대로 였지만, 마지막에 두 타입을 Singular Type으로 묶어주기 위해선 Merge 과정이 필요하다.

     

     

    반응형
Designed by Tistory.