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

유한별·2025년 3월 28일
0

타입 챌린지 스터디

목록 보기
12/19
post-thumbnail

[medium] 4499. Chunk

문제

주어진 배열을 특정 길이의 청크로 나누는 문제입니다.

lodash를 아시나요? Chunk는 lodash에서 매우 유용한 함수입니다. 이제 이것을 구현해보겠습니다.
Chunk<T, N>는 두 개의 필수 타입 매개변수를 받습니다. T는 반드시 튜플이어야 하고, N은 반드시 1 이상의 정수여야 합니다.

예시

type exp1 = Chunk<[1, 2, 3], 2>; // 예상 결과: [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4>; // 예상 결과: [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1>; // 예상 결과: [[1], [2], [3]]

문제 설명

  • 이 문제는 주어진 배열을 특정 길이의 청크(배열)로 나누는 타입을 구현
  • 주어진 배열이 튜플인지 확인하고, 청크의 길이(N)가 1 이상인지 확인
  • 청크의 길이가 배열의 길이보다 클 경우, 배열 자체를 반환

시도 1 (정답)

접근 방법

  • 두 개의 배열을 만들어서, 하나는 청크를 만들고 하나는 청크를 채워나가는 방식
  • 청크를 채워나가는 배열의 길이가 N과 같아지면, 청크를 만들어주는 배열에 추가
  • 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복

코드

type Chunk<
  T extends any[],
  N extends Number,
  Result extends any[] = [],
  Chunk extends any[] = []
> = T extends [infer First, ...infer Rest]
  ? Chunk["length"] extends N
    ? Chunk<Rest, N, [...Result, Chunk], [First]>
    : Chunk<Rest, N, Result, [...Chunk, First]>
  : Chunk["length"] extends 0
  ? Result
  : [...Result, Chunk];

코드 설명

  • Result는 최종적으로 반환될 배열(Chunk[])
  • Chunk는 청크를 채워나가는 배열
  • T를 재귀적으로 탐색하면서, Chunk에 채워넣기
  • Chunk의 길이가 N과 같아지면, Result에 추가
  • 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복

[medium] 4518. Fill

문제

Fill은 JavaScript의 일반적인 함수입니다. 이제 타입으로 이를 구현해보겠습니다.
Fill<T, N, Start?, End?>에서 볼 수 있듯이, Fill은 네 가지 타입의 매개변수를 받습니다. 이 중 TN은 필수 매개변수이고, StartEnd는 선택적 매개변수입니다.
이러한 매개변수들의 요구사항은 다음과 같습니다: T는 반드시 tuple이어야 하고, N은 어떤 타입의 값이든 될 수 있으며, StartEnd는 0보다 크거나 같은 정수여야 합니다.

예시

type exp = Fill<[1, 2, 3], 0>; // 예상 결과: [0, 0, 0]

문제 설명

  • 이 문제는 Fill 함수를 타입으로 구현
  • Fill 함수는 배열 T와 값 N을 받아서, 배열의 일부를 N으로 채우는 함수
  • StartEnd는 선택적 매개변수로, 채우기를 시작할 인덱스와 끝날 인덱스를 지정

시도 1 (정답)

접근 방법

  • 배열을 재귀적으로 탐색하면서, 특정 인덱스 범위에 있는 요소들을 N으로 채우기
  • StartEnd를 지정하지 않으면, 배열의 처음부터 끝까지 채우기
  • 채워야되는 index인지 확인하기 위해 Index 배열을 만들어서 체크
  • Index 배열의 길이가 Start와 같아지면, Switchtrue로 변경
  • Index 배열의 길이가 End와 같아지면, Switchfalse로 변경
  • End 도달하기 전에 T가 끝나면 Result 반환

코드

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T["length"],
  Index extends unknown[] = [],
  Switch extends boolean = false,
  Result extends any[] = []
> = T extends [infer First, ...infer Rest]
  ? Switch extends true
    ? // Switch가 true인 경우(Index가 End보다 클 경우)
      Index["length"] extends End
      ? // 끝났으면 남은 배열 추가(Index가 End와 같아지면 끝나는 것이므로)
        [...Result, ...T]
      : // 아직 끝나지 않았으면 다음 요소 추가
        Fill<Rest, N, Start, End, [...Index, unknown], Switch, [...Result, N]>
    : // Switch가 false인 경우(Index가 Start보다 작을 경우)
    Index["length"] extends Start
    ? // Switch가 false인데 Start와 같아졌을 경우
      Start extends End
      ? // Start와 End가 같으면 N으로 변경 X(원래 값 추가)
        [...Result, First, ...Rest]
      : // Start와 End가 같지 않으면 N으로 변경, Switch를 true로 변경
        Fill<Rest, N, Start, End, [...Index, unknown], true, [...Result, N]>
    : // Switch가 false인데 Start보다 작지 않을 경우 그냥 진행
      Fill<Rest, N, Start, End, [...Index, unknown], Switch, [...Result, First]>
  : Result;

코드 설명

  • T를 재귀적으로 탐색하면서 First 요소 확인
  • Switchtrue인 경우,
    • Index 배열의 길이가 End와 같아지면 끝나는 것이므로 남은 배열 추가
    • 아직 끝나지 않았으면 다음 요소 추가
  • Switchfalse인 경우,
    • Index 배열의 길이가 Start와 같아지면 채워야되는 요소가 되므로 N으로 변경, Switchtrue로 변경
    • 아직 채워야되는 요소가 아니면 그냥 진행
  • End 도달하기 전에 T가 끝나면 Result 반환

[medium] 4803. Trim Right

문제

문자열 타입을 받아 끝부분의 공백을 제거한 새로운 문자열을 반환하는 TrimRight<T>를 구현하세요.

예시

type Trimed = TrimRight<"   Hello World    ">; // expected to be '   Hello World'

문제 설명

  • 문자열 타입을 받아 끝부분의 공백을 제거한 새로운 문자열을 반환

시도 1 (정답)

접근 방법

  • 템플릿 리터럴로 마지막 글자에 whitespaces가 들어가는 지 확인 후 들어가면 재귀

코드

type TrimRight<S extends string> = S extends `${infer T}${" " | "\n" | "\t"}`
  ? TrimRight<T>
  : S;

코드 설명

  • S를 재귀적으로 탐색하면서 T 요소 확인
  • T 요소가 whitespaces(' ' | '\n' | '\t')가 들어가는 지 확인
  • whitespaces가 들어가면 재귀
  • whitespaces가 들어가지 않으면 그냥 반환

[medium] 5117. Without

문제

Lodash의 without 함수를 타입 버전으로 구현하세요. Without<T, U>는 배열 T와 숫자 또는 배열 U를 받아서 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 []

문제 설명

  • 배열 T와 숫자 또는 배열 U를 받아서 U의 요소들을 제외한 배열을 반환
  • U는 숫자 또는 배열이 들어올 수 있음

시도 1 (정답)

접근 방법

  • 배열 T를 재귀적으로 탐색하면서 U의 요소들을 제외한 배열을 반환

코드

type TupleToUnion<T extends readonly number[] | number> = T extends number[]
  ? T[number]
  : T;

type Without<T extends any[], U extends number | number[]> = T extends [
  infer First,
  ...infer Rest
]
  ? First extends TupleToUnion<U>
    ? [...Without<Rest, U>]
    : [First, ...Without<Rest, U>]
  : T;

코드 설명

  • TupleToUnion<U>U가 튜플일 경우 유니온 타입으로 변환
  • Without<T, U>는 배열 T를 재귀적으로 탐색하면서 U의 요소들을 제외한 배열을 반환

[medium] 5140. Trunc

문제

문자열이나 숫자를 받아서 소수점 이하 자릿수를 제거하여 정수 부분만 반환하는 Math.trunc의 타입 버전을 구현하세요.

예시:

type A = Trunc<12.34>; // 12

시도 1

접근 방법

  • number일 경우 string으로 변환
  • 변환된 문자열에서 소수점 이하 자릿수를 제거하여 정수 부분만 반환

코드

type Trunc<
  T extends number | string,
  S = `${T}`
> = S extends `${infer First}.${infer _}` ? `${First}` : S;

실패 원인

  • .3, -.3 등의 예외 케이스 처리 실패

시도 2 (정답)

접근 방법

  • 만약 infer로 추론한 First가 빈 문자열이거나 -일 경우 0으로 처리
  • 그 외에는 그대로 반환

코드

type Trunc<
  T extends number | string,
  S = `${T}`
> = S extends `${infer First}.${infer _}`
  ? First extends "" | "-"
    ? `${First}0`
    : First
  : S;

코드 설명

  • ST를 문자열로 변환한 결과
  • S.를 포함할 경우, First가 빈 문자열이거나 -일 경우 First+0으로 처리
  • 아닐 경우 First를 그대로 반환
  • S.를 포함하지 않을 경우, S를 그대로 반환

[medium] 5153. IndexOf

문제

배열의 Array.indexOf 메서드의 타입 버전을 구현하세요. indexOf<T, U>는 배열 T와 임의의 값 U를 받아서 배열 T에서 처음 등장하는 U의 인덱스를 반환합니다.

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

시도 1

접근 방법

  • index를 계산하는 어레이를 하나 둬서 length로 어레이 값 리턴
  • 튜플을 infer로 하나씩 확인하며 index Array에 요소 하나씩 증가

코드

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

실패 원인

  • number와 같은 타입, 그리고 any를 제대로 처리하지 못함
  • First extends U 대신 더 강력한 타입비교를 만들어야 할 듯

시도 2 (정답)

접근 방법

  • 기존에 사용한 MyEqual 타입을 가져와서 First extends U 대신 사용

코드

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

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

참고

profile
세상에 못할 일은 없어!

0개의 댓글