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

유한별·2025년 3월 19일
1

타입 챌린지 스터디

목록 보기
11/19
post-thumbnail

[medium] 4179. Flip

문제

객체의 키와 값을 서로 바꾸는 타입을 구현하세요.

제약사항

  1. 객체의 값은 string, number, boolean 타입만 가능합니다.
  2. 키가 될 수 있는 타입은 string, number, symbol만 가능하므로, 새로운 키는 반드시 문자열로 변환되어야 합니다.
  3. 중첩된 객체는 지원하지 않습니다.
  4. 배열과 같이 객체의 키가 될 수 없는 값들은 지원하지 않습니다.

예시

Flip<{ a: "x"; b: "y"; c: "z" }>; // {x: "a", y: "b", z: "c"}
Flip<{ a: 1; b: 2; c: 3 }>; // {1: "a", 2: "b", 3: "c"}
Flip<{ a: false; b: true }>; // {false: "a", true: "b"}

시도 1

접근 방식

  • 우선 객체의 키 값들을 keyof로 순회한다.
  • 순회한 키 값으로 T[K]를 키 값으로, K를 value 값으로 넣으면 되지 않을까?

코드

type Flip<T> = { [K in keyof T as T[K] extends PropertyKey ? T[K] : never]: K };

실패 이유

  • boolean 값이 키 값으로 안들어가지는 이슈가 있음
  • PropertyKey는 string, number, symbol 타입만 가능하므로, boolean 값이 키 값으로 들어가지 않음

시도 2 (정답)

접근 방식

  • 1번 시도와 마찬가지로 접근
  • 키 값을 PropertyKey로 제한하는 대신, string, number, boolean 타입을 키 값으로 사용할 수 있도록 제한
  • 이후 키 값을 문자열로 변환하여 사용

코드

type Flip<T> = {
  [K in keyof T as T[K] extends string | number | boolean
    ? `${T[K]}`
    : never]: K;
};

코드 설명

  • T[K] extends string | number | boolean 형태로 키 값을 제한
  • 이후 키 값을 문자열로 변환하여 사용
  • 기존 키 값은 밸류 값으로 사용

[medium] 4182. Fibonacci Sequence

문제

숫자 T를 입력받아 해당하는 피보나치 수를 반환하는 제네릭 타입 Fibonacci<T>를 구현하세요.
수열은 다음과 같이 시작됩니다:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

예시

type Result1 = Fibonacci<3>; // 2
type Result2 = Fibonacci<8>; // 21

시도 1 (정답)

접근 방식

  • 재귀를 통해 피보나치 수열을 구현
  • 숫자 더하기 연산이 안되니까 배열에 뭔가를 넣고, 그 배열의 length를 이용해보자

코드

type Fibonacci<
  T extends number,
  Fibo1 extends any[] = ["f"],
  Fibo2 extends any[] = ["f"],
  Count extends any[] = ["f", "f", "f"]
> = T extends 1 | 2
  ? 1
  : Count["length"] extends T
  ? [...Fibo1, ...Fibo2]["length"]
  : Fibonacci<T, Fibo2, [...Fibo1, ...Fibo2], [...Count, "f"]>;

코드 설명

  • T extends 1 | 2 형태로 초기 조건 설정(T가 1, 2일 때 항상 1)
  • T가 3 이상일 때 CountlengthT와 같아지면 앞의 두 배열을 합한 배열의 length를 반환
  • 만약 CountlengthT보다 작으면, 뒤의 배열을 앞의 배열에 할당하고, 뒤의 배열은 앞의 배열과 뒤의 배열을 합친 배열을 할당
  • 이후 Count"f"를 추가하여 재귀 호출

[medium] 4260. All Combinations

문제

문자열 S의 문자들을 최대 한 번씩만 사용하여 만들 수 있는 모든 문자열 조합을 반환하는 AllCombinations<S> 타입을 구현하세요.

예시

type AllCombinations_ABC = AllCombinations<"ABC">;
// 결과값: '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'

시도 1

접근 방법

  • 우선 문자열을 union으로 변환
  • union을 기반으로 permutation 생성
  • 생성된 요소들을 다시 합쳐 string으로 변환

코드

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

type StringToUnion<S extends string> = StringToTuple<S>[number];

type Permutation<T extends string[], K = T> = [T] extends [never]
  ? []
  : K extends K
  ? [K, ...Permutation<Exclude<T, K>>]
  : never;

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

type AllCombinations<
  S extends string,
  P = Permutation<StringToUnion<S>>
> = P extends P ? Join<P> : never;

코드 설명

  • StringToTuple 함수는 문자열을 튜플로 변환
  • StringToUnion 함수는 튜플을 union으로 변환
  • Permutation 함수는 주어진 배열의 순열을 생성
  • Join 함수는 배열을 문자열로 결합

실패 이유

  • Permutation을 활용하면 안됨
  • 왜냐하면 모든 요소를 사용한 조합만 볼 수 있음
  • 예를 들어 ABC의 경우 ABC, ACB, BAC, BCA, CAB, CBA 이렇게 6개의 요소만 확인 가능
  • 하지만 Exclude를 활용해 접근하는 방식은 맞을 듯

시도 2 (답지 확인)

코드

type StringToUnion<S extends string> = S extends `${infer L}${infer R}`
  ? L | StringToUnion<R>
  : S;

type Combination<A extends string, B extends string> =
  | A
  | B
  | `${A}${B}`
  | `${B}${A}`;

type UnionCombination<A extends string, B extends string = A> = A extends B
  ? Combination<A, UnionCombination<Exclude<B, A>>>
  : never;

type AllCombinations<S extends string> = UnionCombination<StringToUnion<S>>;

코드 설명

  • StringToUnion 함수는 문자열을 union으로 변환 ('' 포함)
  • Combination 함수는 두 문자열을 이용해 조합 생성
  • UnionCombination 함수는 string union으로 들어온 A를 순회하며, 각 요소와 해당 요소를 제외한 union을 재귀적으로 호출하여 조합 생성
  • AllCombinations 함수는 UnionCombination 함수를 활용하여 모든 문자열 조합을 반환

새롭게 배운 점

StringToUnion 함수 구현 방식

type StringToUnion<S extends string> = S extends `${infer L}${infer R}`
  ? L | StringToUnion<R>
  : S;
  • template literal의 infer 구문을 활용해 문자열을 분리하고, 이를 재귀적으로 처리

[medium] 4425. Greater Than

문제

이 챌린지에서는 T > U와 같은 GreaterThan<T, U> 타입을 구현해야 합니다.
음수는 고려하지 않아도 됩니다.

예시

GreaterThan<2, 1>; //true가 되어야 함
GreaterThan<1, 1>; //false가 되어야 함
GreaterThan<10, 100>; //false가 되어야 함
GreaterThan<111, 11>; //true가 되어야 함

시도 1 (정답)

접근 방법

  • 두 숫자를 문자열로 변환하여 자릿수 비교
  • 자릿수가 같다면 맨 앞글자에서부터 확인
  • 자릿수가 다르다면 자릿수 비교

코드

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

// N 크기의 unknown[] 생성
type BuildArray<
  N extends number,
  Arr extends unknown[] = []
> = Arr["length"] extends N ? Arr : BuildArray<N, [...Arr, unknown]>;

// T가 U 이상인지 확인(같거나 더 클 경우)
type CompareDigit<T extends number, U extends number> = BuildArray<T> extends [
  ...BuildArray<U>,
  ...infer Rest
]
  ? true
  : false;

// 맨 앞의 0을 날려줘야 함
type StringToNumber<S extends string> =
  S extends `${"0"}${infer Rest extends number}`
    ? Rest
    : S extends `${infer N extends number}`
    ? N
    : never;

// string[]을 합쳐 string으로 변환
type Join<T extends string[]> = T extends [
  infer First extends string,
  ...infer Rest extends string[]
]
  ? `${First}${Join<Rest>}`
  : "";

type GreaterThan<
  T extends number,
  U extends number,
  TupleT extends string[] = StringToTuple<`${T}`>,
  TupleU extends string[] = StringToTuple<`${U}`>
> =
  // 같은 값인지 비교
  T extends U
    ? false
    : // 두 숫자의 자릿수가 같은지 비교
    TupleT["length"] extends TupleU["length"]
    ? TupleT["length"] extends 1
      ? // 자릿수가 모두 1일 경우, 한자리수 비교
        CompareDigit<T, U>
      : // 실제 앞에서부터 한글자씩 비교 로직
      TupleT extends [
          infer TFirst extends string,
          ...infer TRest extends string[]
        ]
      ? TupleU extends [
          infer UFirst extends string,
          ...infer URest extends string[]
        ]
        ? TFirst extends UFirst
          ? GreaterThan<
              StringToNumber<Join<TRest>>,
              StringToNumber<Join<URest>>
            >
          : CompareDigit<StringToNumber<TFirst>, StringToNumber<UFirst>>
        : true
      : false
    : // 자릿수가 다를 경우, length들끼리 비교
      CompareDigit<TupleT["length"], TupleU["length"]>;

코드 설명

  • 같은 값일 경우 false 리턴

  • 이후 두 숫자의 자릿수가 같은지 비교

  • 자릿수가 같고 한자리수일 경우, 두 숫자를 비교하여 결과 리턴

  • 자릿수가 같고 한자리수가 아닐 경우, 재귀로 비교(앞의 한 자리씩)

  • 자릿수가 다를 경우, 각 자릿수를 비교하여 결과 리턴

  • StringToTuple 함수는 문자열을 튜플로 변환

  • BuildArray 함수는 숫자에 따라 그 길이만큼의 unknown[] 타입을 생성

  • CompareDigit 함수는 두 숫자를 비교하여 결과 리턴

  • StringToNumber 함수는 문자열을 숫자로 변환

  • Join 함수는 튜플을 문자열로 변환

[medium] 4471. Zip

문제

이 챌린지에서는 Zip<T, U> 타입을 구현해야 합니다. 여기서 TU는 반드시 Tuple 이어야 합니다.

예시

type exp = Zip<[1, 2], [true, false]>; // expected to be [[1, true], [2, false]]

문제 설명

  • 이 문제는 두 개의 튜플을 받아서 각각의 같은 위치에 있는 요소들을 쌍으로 묶어 새로운 튜플을 만드는 타입을 구현

시도 1 (정답)

접근 방법

  • 두 개의 튜플의 맨 앞자리를 infer로 추출해서, 둘 다 존재할 경우 새로운 튜플에 추가로 넣어주기
  • 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복

코드

type Zip<T extends any[], U extends any[]> = T extends [
  infer TFirst,
  ...infer TRest
]
  ? U extends [infer UFirst, ...infer URest]
    ? [[TFirst, UFirst], ...Zip<TRest, URest>]
    : []
  : [];

코드 설명

  • 두 개의 튜플에 모두 첫번째 요소가 존재할 경우, 두 요소를 묶은 튜플을 만들고, 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복
  • 만약 둘 중에 하나라도 첫번째 요소가 비어있을 경우(길이가 다를 경우), 빈 배열을 반환하여 재귀 종료

[medium] 4484. IsTuple

문제

IsTuple이라는 타입을 구현하세요. 이 타입은 입력 타입 T를 받아서 T가 튜플 타입인지 여부를 반환합니다.

예시

type case1 = IsTuple<[number]>; // true
type case2 = IsTuple<readonly [number]>; // true
type case3 = IsTuple<number[]>; // false

문제 설명

  • 이 문제는 입력 타입 T가 튜플인지 여부를 확인하는 타입을 구현하는 것입니다.
  • 튜플은 고정된 길이를 가진 배열 타입으로, 각 요소의 타입이 개별적으로 정의될 수 있습니다.
  • 일반 배열과 튜플을 구분할 수 있어야 합니다.
  • readonly 튜플도 처리할 수 있어야 합니다.

시도1 (정답)

접근 방법

  • 튜플은 고정된 길이를 가진 배열 타입이므로 readonly인지, 그리고 길이가 존재하는지 확인

코드

type IsTuple<T> = [T] extends [never]
  ? false
  : T extends readonly any[]
  ? number extends T["length"]
    ? false
    : true
  : false;

코드 설명

  • 우선 never인 경우 false 처리
  • 그 다음, T가 readonly 배열인지 확인
  • readonly 배열인 경우, 길이가 number인지 확인(길이를 확인할 수 있는지)
profile
세상에 못할 일은 없어!

0개의 댓글