Front-End(Web)/Typescript

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

ttaeng_99 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 과정이 필요하다.

 

 

반응형