Front-End(Web)/Typescript

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

ttaeng_99 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를 반영한다.

반응형