-
[Type-challenges] 난이도 Medium - (9)Front-End(Web)/Typescript 2023. 2. 28. 04:03반응형
Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)
https://github.com/type-challenges/type-challenges
📘 목차 - Medium
- Trim Right
- Without
- Trunc
- IndexOf
- Join
- LastIndexOf
- Unique
- 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 를 응용한 풀이이다. 우측부터 트리밍을 적용해주면 된다. (링크)
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를 반영한다.
반응형'Front-End(Web) > Typescript' 카테고리의 다른 글
[Type-challenges] 난이도 Medium - (11) (0) 2023.03.03 [Type-challenges] 난이도 Medium - (10) (0) 2023.02.28 [Type-challenges] 난이도 Medium - (8) (0) 2023.02.28 [Type-challenges] 난이도 Medium - (7) (0) 2023.02.27 [Type-challenges] 난이도 Medium - (6) (0) 2023.02.27