-
[Type-challenges] 난이도 Medium - (3)Front-End(Web)/Typescript 2023. 2. 14. 03:29반응형
Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)
https://github.com/type-challenges/type-challenges
📘 목차 - Medium
- Permutation
- Length of String
- Flatten
- Append to Object
- Absolute
- String To Union
- Merge
📘 문제 및 풀이
1. Permutation
Implement permutation type that transforms union types into the array that includes permutations of unions.
type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Permutation<'A'>, ['A']>>, Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>, Expect<Equal<Permutation<never>, []>>, ]
🖌 풀이
type Permutation<T, U = T> = [T] extends [never] ? [] : T extends U ? [T, ...Permutation<Exclude<U, T>>] : never
먼저, Permutation은 제네릭 인자를 T로 받되, 이를 분기할 때 참조하기 위해 두 번째 인자(U)에 모든 T를 복사해둔다.
마지막 케이스를 대응하기 위해 T에 never가 포함되는 경우 [] 을 반환한다. 단, 이를 판단하기 위해 T, never를 배열로 감싸줘야한다.
또한, T가 U에 해당하면 재귀적으로 실행해주고 이 때는 T와 현재 T를 제외한 Permutation 배열 타입으로 설정한다.
끝으로, 실행할 필요가 없으면 never로 제외시켜주면 된다. (풀이 참고링크)
2. Length of String
Compute the length of a string literal, which behaves like String#length.
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<LengthOfString<''>, 0>>, Expect<Equal<LengthOfString<'kumiko'>, 6>>, Expect<Equal<LengthOfString<'reina'>, 5>>, Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>, ]
🖌 풀이
type LengthOfString< S extends string, T extends string[] = [] > = S extends `${infer F}${infer R}` ? LengthOfString<R, [F, ...T]> : T["length"];
LengthOfString은 제네릭 인자 2가지를 받는다. S는 문자열을, T는 문자열을 하나씩 자른 배열(split) 이라고 생각하면 된다.
S를 먼저 F와 R로 나누고, 남은 R에 대해서 재귀적으로 LengthOfString을 실행한다. 이 때, T 배열에는 F를 추가해준다.
마지막으로 끝나서 빈 문자열이 되면 T의 length를 반환하면 된다.
3. Flatten
In this challenge, you would need to write a type that takes an array and emitted the flatten array type.
For example:type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Flatten<[]>, []>>, Expect<Equal<Flatten<[1, 2, 3, 4]>, [1, 2, 3, 4]>>, Expect<Equal<Flatten<[1, [2]]>, [1, 2]>>, Expect<Equal<Flatten<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, 5]>>, Expect<Equal<Flatten<[{ foo: 'bar'; 2: 10 }, 'foobar']>, [{ foo: 'bar'; 2: 10 }, 'foobar']>>, ]
🖌 풀이
type Flatten<T extends any[], A extends any[] = []> = T extends [infer F, ...infer R] ? F extends any[] ? Flatten<[...F, ...R], A> : Flatten<R, [...A, F]> : A
Flatten은 제네릭 인자로 T(원본배열), A(flat하는 배열) 2가지를 받는다.
T를 F(첫 번째 인자)와 R(나머지 인자들) 로 나눈 뒤, T에 배열이 남아있으면 Flatten을 재귀적으로, 아니면 A를 반환한다.
단, Flatten을 재귀할 때, 케이스 3, 4처럼 다중 배열인 경우가 있다.
F가 배열이면 원본에서 첫 번째 인자인 F를 한 번 flat해서 현재 F에 대해 다시 실행시키고,
F가 배열이 아닌 요소면 첫 번째 인자는 나머지 R들을, 두 번째 인자인 A에 F를 추가해준다.
4. Append to Object
Implement a type that adds a new field to the interface. The type takes the three arguments. The output should be an object with the new field.
For exampletype Test = { id: '1' } type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type test1 = { key: 'cat' value: 'green' } type testExpect1 = { key: 'cat' value: 'green' home: boolean } type test2 = { key: 'dog' | undefined value: 'white' sun: true } type testExpect2 = { key: 'dog' | undefined value: 'white' sun: true home: 1 } type test3 = { key: 'cow' value: 'yellow' sun: false } type testExpect3 = { key: 'cow' value: 'yellow' sun: false isMotherRussia: false | undefined } type cases = [ Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>, Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>, Expect<Equal<AppendToObject<test3, 'isMotherRussia', false | undefined>, testExpect3>>, ]
🖌 풀이
type AppendToObject<T, U extends string, V> = { [P in keyof T | U]: P extends keyof T ? T[P] : V }
AppendToObject 의 제네릭 인자는 3개로, T(추가 전 객체), U(추가할 key, string으로 특정), V(추가할 value) 이다.
새로운 객체의 키인 P는 keyof T와 U 중 하나에 해당될 것이고, 그 값은 P가 T의 key인 경우 T[P]를 아니면 새로운 V를 넣는다.
5. Absolute
Implement the Absolute type. A type that take string, number or bigint. The output should be a positive number string
For exampletype Test = -100; type Result = Absolute<Test>; // expected to be "100"
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Absolute<0>, '0'>>, Expect<Equal<Absolute<-0>, '0'>>, Expect<Equal<Absolute<10>, '10'>>, Expect<Equal<Absolute<-5>, '5'>>, Expect<Equal<Absolute<'0'>, '0'>>, Expect<Equal<Absolute<'-0'>, '0'>>, Expect<Equal<Absolute<'10'>, '10'>>, Expect<Equal<Absolute<'-5'>, '5'>>, Expect<Equal<Absolute<-1_000_000n>, '1000000'>>, Expect<Equal<Absolute<9_999n>, '9999'>>, ]
🖌 풀이
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer N}` ? N : `${T}`
abs() 와 같은 유틸리티가 있을 줄 알았는데 생각보다 심플한 문제였다.
T를 문자열화 한 뒤, 맨 앞에 '-' 마이너스가 있으면 이를 뺀 부분을, 아니면 문자열한 T를 반환한다.
6. String to Union
Implement the String to Union type. Type take string argument. The output should be a union of input letters
For exampletype Test = '123'; type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<StringToUnion<''>, never>>, Expect<Equal<StringToUnion<'t'>, 't'>>, Expect<Equal<StringToUnion<'hello'>, 'h' | 'e' | 'l' | 'l' | 'o'>>, Expect<Equal<StringToUnion<'coronavirus'>, 'c' | 'o' | 'r' | 'o' | 'n' | 'a' | 'v' | 'i' | 'r' | 'u' | 's'>>, ]
🖌 풀이
type StringToUnion<T extends string> = T extends `${infer F}${infer R}` ? F | StringToUnion<R> : never;
T의 모든 문자를 split() 한 유니온 타입을 만드는 문제다.
문자열이 있다면 앞의 F 그리고 뒤의 R은 재귀적으로 StringToUnion을 적용한 유니온을 반환한다.
7. Merge
Merge two types into a new type. Keys of the second type overrides keys of the first type.
For exampletype foo = { name: string; age: string; } type coo = { age: number; sex: string } type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Foo = { a: number b: string } type Bar = { b: number c: boolean } type cases = [ Expect<Equal<Merge<Foo, Bar>, { a: number b: number c: boolean }>>, ]
🖌 풀이
type Merge<F extends object, S extends object> = { [P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P] : never };
마찬가지로 어려울 듯 했으나, 삼항연산을 두 번 활용하면 쉽게 풀 수 있는 문제였다.
반응형'Front-End(Web) > Typescript' 카테고리의 다른 글
[Type-challenges] 난이도 Medium - (5) (0) 2023.02.23 [Type-challenges] 난이도 Medium - (4) (0) 2023.02.16 [Type-challenges] 난이도 Medium - (2) (0) 2023.02.14 [Type-challenges] 난이도 Medium - (1) (0) 2023.02.06 [Type-challenges] 난이도 Easy - (2) (0) 2023.01.25