Front-End(Web)/Typescript

[Type-challenges] 난이도 Easy - (2)

ttaeng_99 2023. 1. 25. 01:07
반응형

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

 


📘 목차 - Easy

  1. If
  2. Concat
  3. Includes
  4. Push
  5. Unshift
  6. Parameters

 

 

 

📘 문제 및 풀이

 

1. If

Implement the util type If<C, T, F> which accepts condition C, a truthy value T, and a falsy value F. C is expected to be either true or false while T and F can be any type.

For example:
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
  Expect<Equal<If<false, 'a', 2>, 2>>,
]

// @ts-expect-error
type error = If<null, 'a', 'b'>

 

🖌 풀이

type If<C extends boolean, T, F> = C extends true ? T : F;

If 유틸리티 타입을 직접 작성하는 코드다. C가 true면 T 아니면 F 타입을 조건부로 반환해준다.

단, 에러 케이스처럼 C가 Boolean 타입임을 제약해서, 이외의 경우엔 에러를 발생시킨다.

 


2. Concat

Implement the JavaScript Array.concat function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr order

For example:
type Result = Concat<[1], [2]> // expected to be [1, 2]


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Concat<[], []>, []>>,
  Expect<Equal<Concat<[], [1]>, [1]>>,
  Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
  Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]

 

🖌 풀이

type Concat<T extends any[], U extends any[]> = [...T, ...U]

T, U 타입이 각각 배열임을 명시해주고, 이를 spread한 배열(타입)을 생성하면 된다.

 


3. Includes

Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.

For example:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
  Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
  Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
  Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
  Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[1], 1 | 2>, false>>,
  Expect<Equal<Includes<[1 | 2], 1>, false>>,
  Expect<Equal<Includes<[null], undefined>, false>>,
  Expect<Equal<Includes<[undefined], null>, false>>,
]

 

🖌 풀이


1) infer 추론 + 재귀를 활용한 풀이

type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest]
  ? MyEqual<First, U> extends true
    ? true
    : Includes<Rest, U>
  : false

type MyEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
  ? true
  : false

먼저, MyEqual 이라는 타입으로 Equal 유틸리티 타입을 직접 구현했다. 두 값이 같으면 true, 아니면 false 를 반환한다.

 

이제, Includes 타입은 T의 엘리먼트들에 대해 재귀적으로 MyEqual을 돌리면서 U와 비교한다.

이 때, infer를 통해 T배열의 첫 번째 인자와 나머지 인자들을 추론하게끔 한다.

 

 

2) 배열을 객체화한 풀이

type Includes<T extends readonly any[], U> = {
  [t in keyof T]: Equal<T[t], U>
}[number] extends false ? false : true

t는 T 배열타입의 인덱스가 된다.(in keyof)

인덱스(t)를 key로, T[t] 엘리먼트와 U의 Equal 여부를 value로 하는 객체 타입을 만들고,

이것의 [number]로 각 키를 순회 해서 Equal 여부를 반환해주면 된다.

 

 

* in keyof

in 키워드는 index signatures 로 정의된다. 보통, 유니온 타입에 해당 타입이 포함되는지 여부를 연산하기에 Type Guard에 쓰인다.

interface A {
  x: number;
}
interface B {
  y: string;
}

let q: A | B = ...;
if ('x' in q) {
  // q: A
} else {
  // q: B
}

 

in 키워드가 keyof와 결합되면, 배열에서의 mapped type definition 으로도 정의되며이는 배열의 각 인자를 다시 맵핑할 수 있다.

interface Person {
    name: string;
    age: number;
}
type Partial<T> = {
    [P in keyof T]?: T[P]; // P will be each key of T
}
type PersonPartial = Partial<Person>; // same as { name?: string;  age?: number; }

(참고링크)

 


4. Push

Implement the generic version of Array.push

For example:
type Result = Push<[1, 2], '3'> // [1, 2, '3']


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Push<[], 1>, [1]>>,
  Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
  Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
]

 

🖌 풀이

type Push<T extends any[], U> = [...T, U]

위 2번의 Concat의 풀이를 참고했다. T가 배열임을 extends로 명시한 다음 spread 하고, 거기에 U를 추가한 타입이다.

 


5. Unshift

Implement the type version of Array.unshift

For example:
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Unshift<[], 1>, [1]>>,
  Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2]>>,
  Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
]

 

🖌 풀이

type Unshift<T extends any[], U> = [U, ...T]

위 4번의 Push와 아주 유사한 풀이이다. Unshift는 인자를 앞에 추가하므로, U와 spread 순서만 바꿔주면 된다.

 


6. Parameters

Implement the built-in Parameters generic without using it.

For example:
const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}

type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
]

 

🖌 풀이

type MyParameters<T extends (...args: any[]) => any> =
  T extends (...args: infer P) => any ? P : never;

Parameters 유틸리티 타입을 직접 구현하는 풀이다.

T가 함수타입이 맞다면, infer로 추론한 인자들의 타입(배열)인 P를 반환하고 아니면 never(혹은 false)를 반환한다.

 

 

반응형