[TS] Type-Challenges 스터디 13주차

유한별·2025년 4월 3일
0

타입 챌린지 스터디

목록 보기
13/19
post-thumbnail

[medium] 5310. Join

View on GitHub: https://tsch.js.org/5310

문제

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'

문제 설명

  • Array.join() 메서드를 타입 레벨에서 구현
  • 배열 T와 문자열 또는 숫자 U를 받아서 배열 T의 요소를 U로 구분하여 문자열로 반환

제한 사항

  • 배열 T의 요소 타입은 문자열 또는 숫자
  • U는 문자열 또는 숫자

시도 1

접근 방법

  • 배열에서 하나씩 순회하면서, 마지막만 아니면 U 끼워넣어서 리턴하기

코드

type Join<
  T extends (string | number)[],
  U extends string | number
> = T extends [
  infer First extends string | number,
  ...infer Rest extends (string | number)[]
]
  ? T["length"] extends 1
    ? `${First}`
    : `${First}${U}${Join<Rest, U>}`
  : "";

실패 원인

  • U가 존재하지 않는 경우 존재

시도 2 (정답)

접근 방법

  • U가 존재하지 않으면 기본값으로 , 추가

코드

type Join<
  T extends (string | number)[],
  U extends string | number = ","
> = T extends [
  infer First extends string | number,
  ...infer Rest extends (string | number)[]
]
  ? T["length"] extends 1
    ? `${First}`
    : `${First}${U}${Join<Rest, U>}`
  : "";

코드 설명

  • T를 infer로 분리하여 첫번째 요소와 나머지 요소로 분리
  • T["length"]가 1이면 마지막 요소이기 때문에, 그냥 반환
  • 아닐 경우, 첫번째 요소와 U를 묶어서 재귀 호출

[medium] 5317. Last Index Of

View on GitHub: https://tsch.js.org/5317

문제

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

type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2>; // 3
type Res2 = LastIndexOf<[0, 0, 0], 2>; // -1

문제 설명

  • Array.lastIndexOf() 메서드를 타입 레벨에서 구현
  • 배열 T와 값 U를 받아서, 배열의 마지막 U의 인덱스를 반환

제한 사항

  • T는 배열
  • U는 배열의 요소 타입

시도 1 (정답)

접근 방식

  • 기존에 사용한 indexof 로직을 활용
  • index를 만날 때마다 result에 저장
  • 마지막까지 다 돌고 나서 result 반환

코드

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

type LastIndexOf<
  T extends any[],
  U,
  IndexArr extends unknown[] = [],
  Result = -1
> = T extends [infer First, ...infer Rest]
  ? MyEqual<First, U> extends true
    ? LastIndexOf<Rest, U, [...IndexArr, unknown], IndexArr["length"]>
    : LastIndexOf<Rest, U, [...IndexArr, unknown], Result>
  : Result;

코드 설명

  • T를 infer로 분리하여 첫번째 요소와 나머지 요소로 분리
  • MyEqual을 통해 첫번째 요소와 U를 비교
  • 같으면 IndexArr의 길이를 Result에 저장
  • 재귀를 돌 때마다 IndexArrunknown 추가(IndexArr의 길이가 곧 해당 요소의 index)
  • 마지막까지 돌고 나서 Result 반환

[medium] 5360. Unique

View on GitHub: https://tsch.js.org/5360

문제

Implement the type version of Lodash.uniq, Unique<T> 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]

문제 설명

  • Lodash.uniq 메서드를 타입 레벨에서 구현
  • 배열 T를 받아서, 배열의 중복된 값을 제거한 배열을 반환

시도 1

  • 배열을 순회하면서, 요소가 중복되는지 확인
  • 중복되지 않으면 중복 체크용 유니온에 해당 요소 추가

코드

type Unique<
  T extends any[],
  Exists = never,
  Result extends any[] = []
> = T extends [infer First, ...infer Rest]
  ? First extends Exists
    ? Unique<Rest, Exists, Result>
    : Unique<Rest, Exists | First, [...Result, First]>
  : Result;

실패 이유

  • any 등의 타입은 extends로 비교할 수 없음.
  • 따라서 MyEqual 타입을 만들어서 비교해야 함.

시도 2

접근 방식

  • MyEqual 타입을 만들고, 유니온 각 요소를 확인하면서 MyEqual이 단 하나라도 true인지 확인

코드

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

type MyExtends<T, Exists extends any[]> = Exists extends [
  infer First,
  ...infer Rest
]
  ? MyEqual<T, First> extends true
    ? true
    : MyExtends<T, Rest>
  : false;

type Unique<
  T extends any[],
  Exists extends any[] = [],
  Result extends any[] = []
> = T extends [infer First, ...infer Rest]
  ? MyExtends<First, Exists> extends true
    ? Unique<Rest, Exists, Result>
    : Unique<Rest, [...Exists, First], [...Result, First]>
  : Result;

코드 설명

  • MyEqual 타입을 만들고, 유니온 각 요소를 확인하면서 MyEqual이 단 하나라도 true인지 확인
  • 만약 true라면 무시하고 나머지 재귀호출
  • 만약 false라면 해당 요소를 유니온에 추가하고 나머지 재귀호출
  • 마지막에 Result 반환

[medium] 5821. Map Types

View on GitHub: https://tsch.js.org/5821

문제

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
};

Examples:

type StringToNumber = { mapFrom: string; mapTo: number };
MapTypes<{ iWillBeANumberOneDay: string }, StringToNumber>; // gives { iWillBeANumberOneDay: number; }

// Be aware that user can provide a union of types:
type StringToNumber = { mapFrom: string; mapTo: number };
type StringToDate = { mapFrom: string; mapTo: Date };
MapTypes<{ iWillBeNumberOrDate: string }, StringToDate | StringToNumber>; // gives { iWillBeNumberOrDate: number | Date; }

// If the type doesn't exist in our map, leave it as it was:
type StringToNumber = { mapFrom: string; mapTo: number };
MapTypes<
  { iWillBeANumberOneDay: string; iWillStayTheSame: Function },
  StringToNumber
>; // // gives { iWillBeANumberOneDay: number, iWillStayTheSame: Function }

문제 설명

  • MapTypes<T, R> 타입은 객체 T의 타입을 변환하는 타입
  • R은 변환 규칙을 정의하는 타입
  • R의 구조는 다음과 같음 (mapFrommapTo가 있음)
  • R은 여러 개의 타입을 유니온으로 받을 수 있음
  • R에 없는 타입은 그대로 반환

시도 1

  • 객체 T를 순회하면서, T[K]RmapFrom과 같은 타입인지 확인
  • 같으면 mapTo 타입으로 변환
  • 같지 않으면 그대로 반환

코드

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

// mapFrom과 mapTo를 어떻게 꺼내오지?
// type MapTypes<T, R> = {
//   [K in keyof T]: R extends R
//     ? MyEqual<R["mapFrom"], K> extends true
//       ? R["mapTo"]
//       : T[K]
//     : T[K];
// };

// infer로 mapFrom과 mapTo를 꺼내옴
type MapTypes<T, R> = {
  [K in keyof T]: R extends { mapFrom: infer From; mapTo: infer To }
    ? T[K] extends From
      ? To
      : T[K]
    : never;
};

실패 이유

  • 마지막 예제 실패
type example = MapTypes<
  { name: string; date: Date },
  { mapFrom: string; mapTo: boolean } | { mapFrom: Date; mapTo: string }
>;
  • R 모두를 돌면서 각각의 결과의 유니온이 들어옴

시도 2 (답지 확인)

접근 방식

  • 유니온 타입을 순회하면서 변환한 것, 변환하지 않은 것이 유니온으로 들어오지 못하게 처리

코드

type MapTypes<T, R extends { mapFrom: any; mapTo: any }> = {
  [K in keyof T]: T[K] extends R["mapFrom"]
    ? R extends { mapFrom: T[K] }
      ? R["mapTo"]
      : never
    : T[K];
};

코드 설명

  • 우선 R{ mapFrom: any; mapTo: any }로 제한
  • T[K]R["mapFrom"]를 비교
  • 만약 extends가 가능하다면, R{ mapFrom: T[K] }extends 가능한 요소만 남김(만약 extends가 불가능하다면, never 반환)
  • 남은 요소의 mapTo 타입으로 변환
  • 만약 extends가 불가능하다면, T[K] 그대로 반환

[medium] 7544. Construct Tuple

View on GitHub: https://tsch.js.org/7544

문제

Construct a tuple with a given length.

type result = ConstructTuple<2>; // expect to be [unknown, unkonwn]

문제 설명

  • 주어진 길이의 튜플을 생성
  • 주어진 길이만큼 unknown 타입의 요소를 가진 튜플을 반환

시도 1 (정답)

접근 방식

  • 재귀를 통해 튜플을 생성
  • ResultlengthL이 될 때까지 재귀 호출

코드

type ConstructTuple<
  L extends number,
  Result extends unknown[] = []
> = Result["length"] extends L
  ? Result
  : ConstructTuple<L, [...Result, unknown]>;

코드 설명

  • ResultlengthL이 될 때까지 재귀 호출
  • ResultlengthL이 되면 튜플을 반환

의문점

  • ts의 재귀 깊이는 1000인가? 다른 문제에서는 100정도만으로도 터지던데... 정확한 매커니즘을 알고 싶다.

[medium] 8640. Number Range

View on GitHub: https://tsch.js.org/8640

문제

Sometimes we want to limit the range of numbers...

type result = NumberRange<2, 9>; //  | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

문제 설명

  • 주어진 범위의 숫자를 반환

시도 1

접근 방식

  • L만큼의 길이를 가진 arr를 만들고, 그 길이를 계속 늘려가며 그 길이를 배열로 모아주자.

코드

type ConstructTuple<
  L extends number,
  Result extends unknown[] = []
> = Result["length"] extends L
  ? Result
  : ConstructTuple<L, [...Result, unknown]>;

type NumberRange<
  L extends number,
  H extends number,
  Arr extends unknown[] = ConstructTuple<L>
> = Arr["length"] extends H
  ? Arr["length"]
  : Arr["length"] | NumberRange<L, H, [...Arr, unknown]>;

실패 이유

  • 140 이상의 숫자를 넣으면 터짐
  • 재귀 깊이 확인 필요

시도 2

접근 방식

  • ConstructTuple을 사용하지 않고, 재귀를 통해 범위의 숫자를 반환

코드

type NumberRange<
  L extends number,
  H extends number,
  Arr extends unknown[] = [],
  IsCount = false
> = Arr["length"] extends H
  ? H
  : IsCount extends true
  ? Arr["length"] | NumberRange<L, H, [...Arr, unknown], IsCount>
  : Arr["length"] extends L
  ? L | NumberRange<L, H, [...Arr, unknown], true>
  : NumberRange<L, H, [...Arr, unknown], false>;

실패 이유

  • 얘도 140 이상의 숫자를 넣으면 터짐

시도 3 (답지 확인)

접근 방식

  • Utils로 L 길이 만큼의 배열을 만듦(0, 1, 2, ...)
  • L 길이와 H 길이의 배열에서 Exclude를 통해 L 이상 H 미만의 값만 남김
  • 이후 유니온으로 묶어서 반환

코드

type Utils<L, C extends any[] = [], R = L> = C["length"] extends L
  ? R
  : Utils<L, [...C, 0], C["length"] | R>;

type NumberRange<L, H> = L | Exclude<Utils<H>, Utils<L>>;

type example = NumberRange<2, 9>;

코드 설명

  • Utils는 재귀를 통해 L 길이 만큼의 배열을 만듦
  • C["length"] extends L 이라면, C의 길이가 L에 도달
  • 그 때까지 R에는 0부터 L까지의 값이 유니온으로 관리되고 있음
  • 이후 Exclude를 통해 L 이상 H 미만의 값만 남김
  • 이후 유니온으로 묶어서 반환
profile
세상에 못할 일은 없어!

0개의 댓글