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

유한별·2025년 5월 13일
0

타입 챌린지 스터디

목록 보기
18/19
post-thumbnail

[medium] 28333. Public Type

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

문제

Remove the key starting with _ from given type T.

문제 설명

주어진 타입 T에서 _로 시작하는 키를 제거

시도 1 (정답)

접근 방식

  • 타입 T의 키를 순회하면서, _로 시작하는 키를 제거

코드

type PublicType<T extends object> = {
  [K in keyof T as K extends `_${infer _}` ? never : K]: T[K];
};

코드 설명

  • KT의 키를 순회, 템플릿 리터럴 타입을 사용해 _로 시작하는 키를 제거
  • 제거된 키는 never로 처리되어 빠짐
  • 나머지 키와 값은 그대로 반환

[medium] 29650. Extract To Object

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

문제

Implement a type that extract prop value to the interface. The type takes the two arguments. The output should be an object with the prop values. Prop value is object.

예시

type Test = { id: "1"; myProp: { foo: "2" } };
type Result = ExtractToObject<Test, "myProp">; // expected to be { id: '1', foo: '2' }

문제 설명

  • 타입 T에서 주어진 키 U의 값을 추출하여 새로운 객체 타입을 반환
  • 추출된 값은 객체 타입

시도 1 (정답)

접근 방식

  • U에 속하는 놈은 날려버리고, U에 대한 놈만 꺼내와서 풀어준 뒤 두 객체 합치기

코드

type MergedObject<T extends object> = {
  [K in keyof T]: T[K];
};

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

코드 설명

  • MergedObject<T>: 주어진 객체 T의 모든 키와 값을 포함하는 새로운 객체 타입을 생성
  • KT의 키 값을 순회, 만약 해당 키 값이 Uextends되지 않는 경우만 남긴 객체 생성
  • 마지막으로 해당 객체와 T[U]를 합치기

[medium] 29785. Deep Omit

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

문제

Implement a typeDeepOmit, Like Utility types Omit, A type takes two arguments.

예시

type obj = {
  person: {
    name: string;
    age: {
      value: number;
    };
  };
};

type test1 = DeepOmit<obj, "person">; // {}
type test2 = DeepOmit<obj, "person.name">; // { person: { age: { value: number } } }
type test3 = DeepOmit<obj, "name">; // { person: { name: string; age: { value: number } } }
type test4 = DeepOmit<obj, "person.age.value">; // { person: { name: string; age: {} } }

문제 설명

  • 주어진 객체에서 주어진 키를 제거하는 타입
  • 주어진 키가 객체 내부에 중첩되어 있을 경우, 해당 키 또한 제거

시도 1

접근 방식

  • S를 받으면, .을 기준으로 나눠서 하나씩 재귀처리
  • 더이상 .이 없으면, 해당 키를 리턴 (반대로 생각함...)

코드

type DeepOmit<T, S> = S extends `${infer First}.${infer Rest}`
  ? First extends keyof T
    ? Record<First, DeepOmit<T[First], Rest>>
    : never
  : S extends keyof T
  ? T[S]
  : never;

실패 이유

  • 애시당초 문제를 잘못 파악함... 해당 키만 살리는게 아니라, 해당 키만 날려야하는 것.

시도 2 (정답)

접근 방식

  • 마찬가지로, 우선 중첩된 키를 재귀로 처리
  • 최종적으로 마지막 키만 Omit 처리

코드

type DeepOmit<T, S> = S extends `${infer First}.${infer Rest}`
  ? First extends keyof T
    ? {
        [K in keyof T]: K extends First ? DeepOmit<T[K], Rest> : T[K];
      }
    : T
  : S extends keyof T
  ? Omit<T, S>
  : T;

코드 설명

  • S를 받으면, .을 기준으로 나눔(First, Rest)
  • T의 키 요소를 순회하며, 만약 해당 키가 First와 일치하는 경우, DeepOmit<T[K], Rest>을 재귀적으로 호출
  • 만약 일치하지 않는 경우, 해당 키를 그대로 리턴
  • S.가 없을 경우(마지막 프로퍼티인 경우), Omit<T, S> 처리

[medium] 30301. IsOdd

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

문제

return true is a number is odd

문제 설명

  • 주어진 숫자가 홀수인지 짝수인지 판별하는 타입

시도 1

접근 방식

  • 숫자를 문자열로 변환
  • number인지, float인지, e-number인지 확인
  • 마지막 자리 숫자를 확인

코드

type LastChar<S extends string> = S extends `${infer _}${infer R}`
  ? R extends ""
    ? S
    : LastChar<R>
  : never;

type IsOdd<T extends number> = `${T}` extends
  | `${string}.${string}`
  | `${string}e${string}`
  ? false
  : LastChar<`${T}`> extends "1" | "3" | "5" | "7" | "9"
  ? true
  : false;

코드 설명

  • LastChar<S>는 문자열의 마지막 문자를 반환하는 타입
  • 만약 ${T}.${string} 또는 e${string}인 경우, false를 반환
  • 만약 ${T}의 마지막 문자가 1 | 3 | 5 | 7 | 9인 경우, true를 반환
  • 그 외의 경우, false를 반환
  • 3e0의 경우, ${3e0}3으로 인식, 3e23의 경우, ${3e23}{string}e${string}으로 인식

[medium] 30430. Tower of Hanoi

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

문제

Simulate the solution for the Tower of Hanoi puzzle. Your type should take the number of rings as input an return an array of steps to move the rings from tower A to tower B using tower C as additional. Each entry in the array should be a pair of strings [From, To] which denotes ring being moved From -> To.

문제 설명

  • 하노이의 탑 문제를 타입으로 풀어보는 문제
  • 주어진 숫자만큼의 원판을 옮기는 경로를 배열로 반환
  • A, B, C 타워가 있고, 원판을 A -> B로 옮기는 경로를 배열로 반환

시도 1 (답지 참조)

코드

type Hanoi<
  N extends number,
  From = "A",
  To = "B",
  Intermediate = "C",
  CurrentIndex extends unknown[] = []
> = CurrentIndex["length"] extends N
  ? []
  : [
      ...Hanoi<N, From, Intermediate, To, [...CurrentIndex, unknown]>,
      [From, To],
      ...Hanoi<N, Intermediate, To, From, [...CurrentIndex, unknown]>
    ];

코드 설명

  • CurrentIndex 배열의 길이가 N과 같아지면, 빈 배열을 반환 (원판 N개의 이동이 모두 끝난 경우)
  • n-1개의 원판을 보조 기둥으로 이동
  • 가장 아래에 있는 n번째 원판을 바로 목적지로 이동
  • 아까 보조 기둥에 있던 n-1개의 원판을 목적지로 이동
  • 이 과정을 반복하여 원판 N개의 이동이 모두 끝나면, 빈 배열을 반환

[medium] 30958. Pascal's triangle

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

문제

Given a number N, construct the Pascal's triangle with N rows.

문제 설명

  • 파스칼의 삼각형을 타입으로 구현
  • 주어진 숫자만큼의 행을 가진 파스칼의 삼각형을 만들어 반환

시도 1 (답지 참조)

코드

type GetLast<T extends number[][]> = T extends [
  ...any,
  infer L extends number[]
]
  ? L
  : never;

type ToTuple<T extends number, R extends number[] = []> = R["length"] extends T
  ? R
  : ToTuple<T, [...R, 0]>;

type Sum<T extends number, U extends number> = [
  ...ToTuple<T>,
  ...ToTuple<U>
]["length"];

type GenRow<T extends number[], R extends number[] = [1]> = T extends [
  infer F extends number,
  infer S extends number,
  ...infer L extends number[]
]
  ? [Sum<F, S>] extends [infer A extends number]
    ? GenRow<[S, ...L], [...R, A]>
    : never
  : [...R, 1];

type Pascal<
  N extends number,
  R extends number[][] = [[1]]
> = R["length"] extends N ? R : Pascal<N, [...R, GenRow<GetLast<R>>]>;

코드 설명

  • 재귀의 깊이가 N과 같아지면, 파스칼의 삼각형(R)을 반환
  • 그 때까지는 GenRow 함수를 통해 파스칼의 삼각형을 만들어 배열에 추가
  • GenRow 함수는 파스칼의 삼각형의 마지막 행을 가져오고, 이를 더해 새로운 행을 만듦
  • 이 과정을 반복하여 파스칼의 삼각형을 완성
profile
세상에 못할 일은 없어!

0개의 댓글