Front-End(Web)/Typescript

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

ttaeng_99 2023. 2. 14. 03:29
반응형

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. Permutation
  2. Length of String
  3. Flatten
  4. Append to Object
  5. Absolute
  6. String To Union
  7. 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 example
type 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 example
type 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 example
type 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 example
type 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
};

마찬가지로 어려울 듯 했으나, 삼항연산을 두 번 활용하면 쉽게 풀 수 있는 문제였다.

 

반응형