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

유한별·2025년 4월 18일
0

타입 챌린지 스터디

목록 보기
15/19
post-thumbnail

[medium] 9898. Appear Only Once

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

문제

Find the elements in the target array that appear only once. For example:input: [1,2,2,3,3,4,5,6,6,6],ouput: [1,4,5].

문제 설명

  • 배열을 받아, 중복되지 않는 요소를 반환

시도 1 (정답)

접근 방식

  • 배열을 순회하면서, 각 요소가 몇 번 나오는지 카운트
  • 카운트가 1인 요소만 배열로 반환

코드

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

type IsUniqueInArray<
  T extends any[],
  E,
  CountArr extends unknown[] = []
> = T extends [infer First, ...infer Rest]
  ? IsEqual<First, E> extends true
    ? IsUniqueInArray<Rest, E, [...CountArr, unknown]>
    : IsUniqueInArray<Rest, E, CountArr>
  : CountArr["length"] extends 1
  ? true
  : false;

type FindEles<
  T extends any[],
  Original extends any[] = T,
  Result extends any[] = []
> = T extends [infer First, ...infer Rest]
  ? IsUniqueInArray<Original, First> extends true
    ? FindEles<Rest, Original, [...Result, First]>
    : FindEles<Rest, Original, Result>
  : Result;

코드 설명

  • 이번에도 역시나 general type을 정확히 비교하기 위해 기존에 사용한 isEqual 함수를 사용
  • 배열을 순회하며, 각 요소가 몇 번 나오는지 확인
  • 카운트가 1인 요소만 배열로 반환

[medium] 9989. Count Element Number to Object

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

문제

With type CountElementNumberToObject, get the number of occurrences of every item from an array and return them in an object.

예시

type Simple1 = CountElementNumberToObject<[]>; // return {}
type Simple2 = CountElementNumberToObject<[1, 2, 3, 4, 5]>;
// return {
//   1: 1,
//   2: 1,
//   3: 1,
//   4: 1,
//   5: 1
// }

type Simple3 = CountElementNumberToObject<[1, 2, 3, 4, 5, [1, 2, 3]]>;
// return {
//   1: 2,
//   2: 2,
//   3: 2,
//   4: 1,
//   5: 1
// }

문제 설명

  • 배열을 받아, 각 요소의 개수를 객체로 반환

시도 1

접근 방식

  • 우선 배열을 flatten 처리
  • 이후 각 요소를 돌면서 result에 해당 요소가 존재하는지 확인
  • 있을 경우에 value의 카운트만 증가, 없을 경우에는 새로운 레코드 추가

코드

type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
  ? First extends any[]
    ? [...Flatten<First>, ...Flatten<Rest>]
    : [First, ...Flatten<Rest>]
  : [];

type GetResult<T extends Record<string, unknown[]>> = {
  [K in keyof T]: T[K]["length"];
};

type AddResult<
  T extends PropertyKey,
  Result extends Record<string, unknown[]>
> = {
  [K in keyof Result]: K extends `${T}` ? [...Result[K], unknown] : Result[K];
} & (`${T}` extends keyof Result ? {} : { [K in `${T}`]: [unknown] });

type GetCountElement<
  T extends any[],
  Result extends Record<string, unknown[]> = {}
> = T extends [infer First, ...infer Rest]
  ? [First] extends [never]
    ? GetCountElement<Rest, Result>
    : First extends PropertyKey
    ? GetCountElement<Rest, AddResult<First, Result>>
    : GetCountElement<Rest, Result>
  : GetResult<Result>;

type CountElementNumberToObject<T extends any[]> = GetCountElement<Flatten<T>>;

코드 설명

  • 우선 배열을 flatten 처리
  • never 처리를 위해 [First] extends [never] 처리
  • 이후 각 요소를 돌면서 result에 해당 요소가 존재하는지 확인
  • 있을 경우에 value의 카운트 추가(unknown[]), 없을 경우에는 새로운 레코드 추가
  • 이후 GetResult 타입을 통해 객체로 반환

실패 이유

  • 객체 키의 타입을 넣어주기엔 키 값으로 들어갈 수 없는 타입들이 생겨서 분기처리가 너무 복잡해짐

시도 2 (답지 확인)

코드

type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
  ? First extends any[]
    ? [...Flatten<First>, ...Flatten<Rest>]
    : [First, ...Flatten<Rest>]
  : [];

type Count<T, R extends Record<string | number, any[]> = {}> = T extends [
  infer F extends string | number,
  ...infer L
]
  ? F extends keyof R
    ? Count<L, Omit<R, F> & Record<F, [...R[F], 0]>>
    : Count<L, R & Record<F, [0]>>
  : {
      [K in keyof R]: R[K]["length"];
    };

type CountElementNumberToObject<T extends any[]> = [T] extends [[never]]
  ? {}
  : Count<Flatten<T>>;

코드 설명

  • 배열 flatten 처리
  • infer를 활용해 Fstring | number 타입으로 제한
  • Fkeyof R 에 존재하는지 확인
  • 존재할 경우 Count<L, Omit<R, F> & Record<F, [...R[F], 0]>> 처리
    • 기존 객체에서 F 를 제외한 객체를 만들고, F 를 키로 하고 값을 [...R[F], 0] 으로 하는 객체를 만들어 합침 (count 증가)
  • 존재하지 않을 경우 Count<L, R & Record<F, [0]>> 처리
    • R & Record<F, [0]>R 에서 F 를 키로 하고, 값을 [0] 으로 하는 객체
  • T 순회를 마치면 [K in keyof R]: R[K]["length"] 타입을 통해 객체로 반환

[medium] 10969. Integer

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

문제

Please complete type Integer<T>, type T inherits from number, if T is an integer return it, otherwise return never.

문제 설명

  • 숫자를 받아, 정수일 경우 숫자를 반환, 소수일 경우 never 반환

시도 1

접근 방식

  • 우선 숫자인지 확인
  • 숫자일 경우 템플릿 리터럴로 분리해버리기
  • 분리한 템플릿 리터럴에서 소수점 이하 숫자가 0인지 확인

코드

type IsAllZero<T extends string> = T extends `${infer First}${infer Rest}`
  ? First extends "0"
    ? IsAllZero<Rest>
    : false
  : true;

type Integer<T> = [T] extends [number]
  ? number extends T
    ? never
    : `${T}` extends `${infer _Int}.${infer Float}`
    ? IsAllZero<Float> extends true
      ? T
      : never
    : T
  : never;

코드 설명

  • 우선 숫자인지 확인 ([T] extends [number], number extends T)
  • 숫자일 경우 템플릿 리터럴로 분리 (${T} extends ${infer _Int}.${infer Float})
  • 분리한 템플릿 리터럴에서 소수점 이하 숫자가 0인지 확인 (IsAllZero<Float> extends true)
  • 소수점 이하 숫자가 0일 경우 정수 반환, 아닐 경우 never 반환

[medium] 16259. To Primitive

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

문제

Please complete type ToPrimitive<T>, type T inherits from object, convert T to a primitive type.

예시

type X = {
  name: "Tom";
  age: 30;
  married: false;
  addr: {
    home: "123456";
    phone: "13111111111";
  };
};

type Expected = {
  name: string;
  age: number;
  married: boolean;
  addr: {
    home: string;
    phone: string;
  };
};

문제 설명

  • 객체를 받아, 객체의 모든 프로퍼티를 기본 타입으로 변환

시도 1 (더러운 정답)

접근 방식

  • 객체의 프로퍼티를 돌면서 기본 타입으로 변환
  • 배열/객체일 경우 재귀적으로 처리

코드

type InferType<T> = T extends string
  ? string
  : T extends number
  ? number
  : T extends boolean
  ? boolean
  : T extends (...args: any[]) => any
  ? Function
  : T extends any[]
  ? ChangeArray<T>
  : T extends object
  ? { [K in keyof T]: InferType<T[K]> }
  : T;

type ChangeArray<T extends any[]> = {
  [K in keyof T]: InferType<T[K]>;
};

type ChangeObject<T> = T extends (...args: any[]) => any
  ? Function
  : T extends any[]
  ? ChangeArray<T>
  : T extends object
  ? { [K in keyof T]: InferType<T[K]> }
  : T;

type ChangeValue<T> = T extends string
  ? string
  : T extends number
  ? number
  : T extends boolean
  ? boolean
  : T;

type ToPrimitive<T extends object> = T extends [...infer R]
  ? InferType<R>
  : {
      [K in keyof T]: T[K] extends object
        ? ChangeObject<T[K]>
        : InferType<T[K]>;
    };

코드 설명

  • 객체의 프로퍼티를 돌면서 기본 타입으로 변환

  • 기본 타입일 primitive 타입으로 반환

  • 배열/객체일 경우 재귀적으로 처리

  • 그런데 중복된 로직이 많은 것 같아서 하나씩 다 걷어내봄

시도 2 (깔끔한 정답)

코드

type PrimitiveType<T> = T extends string
  ? string
  : T extends number
  ? number
  : T extends boolean
  ? boolean
  : T extends (...args: any[]) => any
  ? Function
  : T extends any[] | object
  ? { [K in keyof T]: PrimitiveType<T[K]> }
  : T;

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

[medium] 17973. DeepMutable

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

문제

Please complete type DeepMutable<T>, type T inherits from object, convert T to a mutable object.

예시

type X = {
  readonly a: () => 1;
  readonly b: string;
  readonly c: {
    readonly d: boolean;
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true;
          readonly j: "s";
        };
        readonly k: "hello";
      };
    };
  };
};

type Expected = {
  a: () => 1;
  b: string;
  c: {
    d: boolean;
    e: {
      g: {
        h: {
          i: true;
          j: "s";
        };
        k: "hello";
      };
    };
  };
};

문제 설명

  • 객체를 받아, 객체의 모든 프로퍼티를 가변 타입(not readonly)으로 변환

시도 1

접근 방식

  • 객체의 프로퍼티를 돌면서 가변 타입으로 변환
  • 객체일 경우 재귀적으로 처리

코드

type isNotPureObject<T extends object> = T extends [...infer _]
  ? true
  : T extends () => {}
  ? true
  : false;

type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object
    ? isNotPureObject<T[K]> extends false
      ? DeepMutable<T[K]>
      : T[K]
    : T[K];
};

코드 설명

  • 객체의 프로퍼티를 순회하면서 가변 타입으로 변환
  • 객체일 경우 재귀적으로 처리
  • 배열 및 함수일 경우 확인해서 그대로 반환

[medium] 18142. All

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

문제

Returns true if all elements of the list are equal to the second parameter passed in, false if there are any mismatches.

예시

type Test1 = [1, 1, 1];
type Test2 = [1, 1, 2];

type Todo = All<Test1, 1>; // should be same as true
type Todo2 = All<Test2, 1>; // should be same as false

문제 설명

  • 배열을 받아, 배열의 모든 요소가 두 번째 파라미터와 같은지 확인

시도 1

접근 방식

  • 배열을 순회하면서 두 번째 파라미터와 같은지 확인
  • 같지 않으면 false 반환
  • 모두 같으면 true 반환

코드

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

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

코드 설명

  • 배열을 순회하면서 두 번째 파라미터와 같은지 확인 (MyEqual 함수 사용)
  • 같지 않으면 false 반환
  • 모두 같으면 true 반환
profile
세상에 못할 일은 없어!

0개의 댓글