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

유한별·2025년 3월 16일
0
post-thumbnail

[Medium] 1978. Percentage Parser

문제

PercentageParser을 구현하세요. /^(\+|\-)?(\d*)?(\%)?$/ 정규식에 따라 T를 일치시키고 3개의 일치 요소를 얻습니다
구조는 [더하기 혹은 빼기, 숫자,단위]와 같아야 합니다.
일치 요소가 없다면, 기본값은 빈 문자열입니다.

예시

type PString1 = "";
type PString2 = "+85%";
type PString3 = "-85%";
type PString4 = "85%";
type PString5 = "85";

type R1 = PercentageParser<PString1>; // expected ['', '', '']
type R2 = PercentageParser<PString2>; // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>; // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>; // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>; // expected ["", "85", ""]

시도 1

접근 방식

  • 맨 앞, 중간 부분, 맨 뒤를 잘라서 확인해보자

코드

type IsSign<C extends string> = C extends "+" | "-" ? true : false;
type IsPercent<C extends string> = C extends "%" ? true : false;
type PercentageParser<A extends string> =
  A extends `${infer First}${infer Middle}${infer Last}`
    ? IsSign<First> extends true
      ? IsPercent<Last> extends true
        ? [First, Middle, Last]
        : [First, `${Middle}${Last}`, ""]
      : IsPercent<Last> extends true
        ? ["", `${First}${Middle}`, Last]
        : ["", `${First}${Middle}${Last}`, ""]
    : ["", "", ""];

실패 이유

  • 중간 부분 분리를 못함

시도 2 (정답)

접근 방식

  • 입력받은 string을 튜플로 바꿔서 확인

코드

type IsSign<C extends string> = C extends "+" | "-" ? true : false;
type IsPercent<C extends string> = C extends "%" ? true : false;

type Last<T extends any[]> = T extends [...infer _, infer Last extends string]
  ? Last
  : "";

type Join<T extends string[]> = T extends [
  infer First extends string,
  ...infer Rest extends string[],
]
  ? `${First}${Join<Rest>}`
  : "";

type RemoveLast<T extends string[]> = T extends [
  ...infer Rest extends string[],
  infer _,
]
  ? Join<Rest>
  : "";

type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
  ? [First, ...StringToTuple<Last>]
  : [];

type PercentageParser<A extends string> =
  StringToTuple<A> extends [
    infer First extends string,
    ...infer Rest extends string[],
  ]
    ? IsSign<First> extends true
      ? IsPercent<Last<Rest>> extends true
        ? [First, RemoveLast<Rest>, Last<Rest>]
        : [First, Join<Rest>, ""]
      : IsPercent<First> extends true
        ? ["", "", "%"]
        : IsPercent<Last<Rest>> extends true
          ? ["", RemoveLast<[First, ...Rest]>, Last<Rest>]
          : ["", Join<[First, ...Rest]>, ""]
    : ["", "", ""];

코드 설명

  • IsSign<C extends string>: 한 글자(Character)를 받아 해당 요소가 부호(+, -)인지 확인
  • IsPercent<C extends string>: 한 글자(Character)를 받아 해당 요소가 단위(%)인지 확인
  • Last<T extends any[]>: 배열을 받아 마지막 요소를 반환
  • Join<T extends string[]>: 배열을 받아 문자열로 결합
  • RemoveLast<T extends string[]>: 배열을 받아 마지막 요소를 제거
  • StringToTuple<S extends string>: 문자열을 받아 튜플로 변환
  • 문자열을 받아 튜플로 변환한 후, 튜플의 첫 번째 요소가 부호인지 확인
  • 이후 튜플의 마지막 요소가 퍼센트인지 확인
  • 각 조건에 맞게 튜플을 문자열로 결합하여 반환

[Medium] 2070. Drop Char

문제

지정된 문자를 문자열에서 제거하는 DropChar<T, C>를 구현하세요.

예시

type Butterfly = DropChar<" b u t t e r f l y ! ", " ">; // 'butterfly!'

시도 1 (정답)

접근 방식

  • 119-ReplaceAll 참고해서 풀어보자

코드

type DropChar<
  S extends string,
  C extends string,
> = S extends `${infer Head}${C}${infer Tail}`
  ? DropChar<`${Head}${Tail}`, C>
  : S;

코드 설명

  • ${infer Head}${C}${infer Tail}를 활용해 S 안에 C가 있는지 확인
  • 있을 경우, Head와 Tail을 재귀적으로 호출하여 C를 제거
  • 없을 경우, S를 그대로 반환

[Medium] 2257. Minus One

문제

주어진 숫자(항상 양수) 타입을 받아 1을 빼는 MinusOne<T>를 구현하세요. 반환 타입은 숫자 타입이어야 합니다.

예시

type Zero = MinusOne<1>; // 0
type FiftyFour = MinusOne<55>; // 54

시도 1 (정답)

접근 방식

  • 받은 숫자를 리터럴로 변경 후, 마지막 자리에서 -1 처리
  • 마지막 자리가 0일 경우, 앞 문자로 넘어오며 재귀 돌리기

코드

// 한 자리 수의 마이너스 1 처리용 타입
type CalcMinusOne = {
  [key: string]: string;
  "1": "0";
  "2": "1";
  "3": "2";
  "4": "3";
  "5": "4";
  "6": "5";
  "7": "6";
  "8": "7";
  "9": "8";
  "0": "9";
};

// 문자열 배열(튜플)의 마지막 요소 추출
type Last<T extends string[]> = T extends [...infer _, infer Last]
  ? Last
  : never;

// 문자열을 튜플로 변환
type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
  ? [First, ...StringToTuple<Last>]
  : [];

// 튜플을 문자열로 변환
type Join<T extends string[]> = T extends [
  infer First extends string,
  ...infer Rest extends string[],
]
  ? `${First}${Join<Rest>}`
  : "";

// 마지막 요소를 제거한 튜플을 문자열로 변환
type RemoveLast<T extends string[]> = T extends [
  ...infer Rest extends string[],
  infer _,
]
  ? Join<Rest>
  : "";

// 문자열을 숫자로 변환
type StringToNumber<S extends string> = S extends `${infer N extends number}`
  ? N
  : never;

// 문자열 배열(튜플)의 마지막 요소가 0인 경우, 마지막 요소를 제거한 문자열 배열(튜플)을 문자열로 변환
// 만약 마지막 요소가 0이면 9로 변환, 그리고 앞 요소에 대해서도 재귀 돌리기
type MinusOneInStringArray<
  S extends string,
  ST extends string[] = StringToTuple<S>,
> =
  Last<ST> extends "0"
    ? S extends "10"
      ? "9"
      : `${MinusOneInStringArray<RemoveLast<ST>>}9`
    : Last<ST> extends "_"
      ? `${MinusOneInStringArray<RemoveLast<ST>>}_`
      : `${RemoveLast<ST>}${CalcMinusOne[Last<ST>]}`;

// 재귀 돌리기 전처리
type MinusOne<T extends number, S extends string[] = StringToTuple<`${T}`>> =
  Last<S> extends "0"
    ? StringToNumber<`${MinusOneInStringArray<RemoveLast<S>>}9`>
    : StringToNumber<`${RemoveLast<S>}${CalcMinusOne[Last<S>]}`>;

코드 설명

  • 1978-PercentageParser 문제에서 작성한 함수들 활용
  • CalcMinusOne 타입: 한 자리 수의 마이너스 1 처리용 타입
  • StringToTuple<S>: 문자열을 튜플로 변환
  • Join<T extends string[]>: 튜플을 문자열로 변환
  • RemoveLast<T extends string[]>: 튜플의 마지막 요소를 제거하여 문자열로 조인해서 반환
  • StringToNumber<S extends string>: 문자열을 숫자로 변환
  • Last<T extends string[]>: 튜플의 마지막 요소 추출
  • MinusOneInStringArray<S>: 문자열 배열(튜플)의 마지막 요소가 0인 경우, 마지막 요소를 제거한 문자열 배열(튜플)을 문자열로 변환
  • MinusOne<T>: 재귀 돌리기, 최초 조건부 처리

[Medium] 2595. Pick By Type

문제

T에서 U 타입을 할당할 수 있는 속성만 선택하는 PickByType<T, U>를 구현하세요.

예시

type OnlyBoolean = PickByType<
  {
    name: string;
    count: number;
    isReadonly: boolean;
    isEnable: boolean;
  },
  boolean
>; // { isReadonly: boolean; isEnable: boolean; }

시도 1 (정답)

접근 방식

  • 1130-replace-keys 문제를 참고하여 각 key-value를 순회하여 value의 타입을 확인해보자

코드

type PickByType<T, U> = { [K in keyof T as T[K] extends U ? K : never]: T[K] };

코드 설명

  • T[K] extends U ? K : never 조건의 키-리맵핑을 통해 T[K]의 타입이 U에 할당 가능한 경우, 해당 키만 남기고 나머지는 제거

[Medium] 2688. StartsWith

문제

문자열 T가 문자열 U로 시작하는지 확인하는 StartsWith<T, U>를 구현하세요.

예시

type a = StartsWith<"abc", "ac">; // expected to be false
type b = StartsWith<"abc", "ab">; // expected to be true
type c = StartsWith<"abc", "abcd">; // expected to be false

시도 1 (정답)

접근 방식

  • infer를 통해 맨 앞 한글자씩을 비교해보자
  • 죽도록 비교해서 지는 쪽이 false가 되도록...

코드

type StartsWith<T extends string, U extends string> = T extends U
  ? true
  : T extends `${infer TFirst}${infer TRest}`
    ? U extends `${infer UFirst}${infer URest}`
      ? TFirst extends UFirst
        ? StartsWith<TRest, URest>
        : false
      : true
    : false;

코드 설명

  • T extends U 조건: 문자열 T가 U와 일치하는지 확인
  • 일치하지 않을 경우, 각각의 첫 글자끼리 비교
  • 만약 각각의 첫 글자가 일치할 경우, 나머지 문자열끼리 비교(재귀)
  • 만약 각각의 첫 글자가 일치하지 않을 경우, false 반환
  • T가 남아있는데(TFirst와 TRest로 분리되는데) U가 남아있지 않을 경우 true 반환

[Medium] 2693. Ends With

문제

문자열 T가 문자열 U로 끝나는지 확인하는 EndsWith<T, U>를 구현하세요.

예시

type a = EndsWith<"abc", "bc">; // expected to be true
type b = EndsWith<"abc", "abc">; // expected to be true
type c = EndsWith<"abc", "d">; // expected to be false

시도 1 (정답)

접근 방식

  • startsWith 문제를 참고해서 풀어보자
  • 문자열을 뒤집어서 확인하는 방식으로 해결

코드

type ReverseString<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${ReverseString<Rest>}${First}`
  : S;

type StartsWith<T extends string, U extends string> = T extends U
  ? true
  : T extends `${infer TFirst}${infer TRest}`
    ? U extends `${infer UFirst}${infer URest}`
      ? TFirst extends UFirst
        ? StartsWith<TRest, URest>
        : false
      : true
    : false;

type EndsWith<T extends string, U extends string> = StartsWith<
  ReverseString<T>,
  ReverseString<U>
>;

코드 설명

  • ReverseString<T>: 문자열 T를 뒤집은 문자열
  • StartsWith<ReverseString<T>, ReverseString<U>>: 뒤집은 문자열 T와 U를 비교
profile
세상에 못할 일은 없어!

0개의 댓글