icon

메티의 블로그

10주차 타입챌린지 스터디
10주차 타입챌린지 스터디

10주차 타입챌린지 스터디

Tags
TypeScript
날짜
Mar 12, 2025
상태
공개

3188 - Tuple to Nested Object

문제: Given a tuple type T that only contains string type, and a type U, build an object recursively.
/* _____________ 여기에 코드 입력 _____________ */ type Reverse<T extends unknown[], C extends unknown[] = []> = T extends [infer First, ...infer Rest] ? Reverse<Rest, [First, ...C]> : C; type RecursiveBuilder<T, U, C = U> = T extends [infer First, ...infer Rest] ? RecursiveBuilder<Rest, U, {[K in First extends string ? First : never]: C}> : C; type TupleToNestedObject<T extends unknown[], U> = RecursiveBuilder<Reverse<T>, U> type Test = TupleToNestedObject<['a', 'b'], string> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TupleToNestedObject<['a'], string>, { a: string }>>, Expect<Equal<TupleToNestedObject<['a', 'b'], number>, { a: { b: number } }>>, Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, { a: { b: { c: boolean } } }>>, Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>, ]
  • 풀이
    • 살짝 억지 풀이…
    • RecursiveBuilder 를 통해 정답과 역순으로 nested 객체를 만드는 것을 확인하여, 인풋으로 받는 T 배열을 뒤집어 풀이함
  • 배운 점
    • 키 순회 시, 항상 keyof T 만 가능한 것은 아니다. 다른 타입을 infer 로 가져와서 순회 돌게 할 수 있다.
      • {[K in First extends string ? First : never]: C}
    • 두 타입은 동치이다.
      • type Example1 = T extends string ? T : never; type Example2 = T & string;

3192 - Reverse

문제: Implement the type version of Array.reverse
/* _____________ 여기에 코드 입력 _____________ */ type ReverseBuilder<T, C extends unknown[] = []> = T extends [infer First, ...infer Rest] ? ReverseBuilder<Rest, [First, ...C]> : C; type Reverse<T extends unknown[]> = ReverseBuilder<T>; // type Reverse<T extends any[]> = T extends [infer F, ...infer Rest] ? [...Reverse<Rest>, F] : T; type Test = Reverse<['a','b']> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Reverse<[]>, []>>, Expect<Equal<Reverse<['a', 'b']>, ['b', 'a']>>, Expect<Equal<Reverse<['a', 'b', 'c']>, ['c', 'b', 'a']>>, ] type errors = [ // @ts-expect-error Reverse<'string'>, // @ts-expect-error Reverse<{ key: 'value' }>, ]
  • 풀이
    • 기존에도 많이 사용하던 풀이였으나, 해답을 보니 누적 배열 없이도 가능하다.
  • 배운 점
    • 누적 배열 없이 배열 리턴 후 rest 연산으로 쌓아도 된다.
 

3196 - Flip Arguments

문제: Implement the type version of lodash's _.flip. Type FlipArguments<T> requires function type T and returns a new function type which has the same return type of T but reversed parameters.
/* _____________ 여기에 코드 입력 _____________ */ type ReverseBuilder<T extends unknown[], C extends unknown[] = []> = T extends [infer First, ...infer Rest] ? ReverseBuilder<Rest, [First, ...C]> : C; type FlipArguments<T extends Function> = T extends (...args: infer Args) => infer Returns ? (...args: ReverseBuilder<Args>) => Returns : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FlipArguments<() => boolean>, () => boolean>>, Expect<Equal<FlipArguments<(foo: string) => number>, (foo: string) => number>>, Expect<Equal<FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>, (arg0: boolean, arg1: number, arg2: string) => void>>, ] type errors = [ // @ts-expect-error FlipArguments<'string'>, // @ts-expect-error FlipArguments<{ key: 'value' }>, // @ts-expect-error FlipArguments<['apple', 'banana', 100, { a: 1 }]>, // @ts-expect-error FlipArguments<null | undefined>, ]
  • 풀이
    • 함수 args 배열의 타입을 뒤집어 변경

3243 - FlattenDepth

문제: Recursively flatten array up to depth times.
/* _____________ 여기에 코드 입력 _____________ */ // type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest] ? First extends [...infer Inner] ? [...Inner, ...Flatten<Rest>] : [First, ...Flatten<Rest>] : T; // type FlattenTest1 = Flatten<[1, 2, 3]>; // type FlattenTest2 = Flatten<[1, [2], [[3]]]>; // type Builder<T extends unknown[], Depth extends number, Acc extends any[] = []> = Acc['length'] extends Depth ? T : Builder<Flatten<T>, Depth, [...Acc, never]>; // type FlattenDepth<T extends unknown[], Depth extends number = 1> = Builder<T, Depth>; // type Test1 = FlattenDepth<[1, [[2]]]>; // type Test2 = FlattenDepth<[1, 2, [3, 4], [[5]]], 2>; type Builder<T extends unknown[], Depth extends number, Acc extends never[] = []> = Acc['length'] extends Depth ? T : T extends [infer First, ...infer Rest] ? First extends any[] ? [...Builder<First, Depth, [...Acc, never]>, ...Builder<Rest, Depth, Acc>] : [First, ...Builder<Rest, Depth, Acc>] :T; type FlattenDepth<T extends unknown[], Depth extends number = 1> = Builder<T, Depth>; type Test1 = FlattenDepth<[1, [[2]]]>; type Test2 = FlattenDepth<[1, 2, [3, 4], [[5]]], 2>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FlattenDepth<[]>, []>>, Expect<Equal<FlattenDepth<[1, 2, 3, 4]>, [1, 2, 3, 4]>>, Expect<Equal<FlattenDepth<[1, [2]]>, [1, 2]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>, [1, 2, 3, 4, [5]]>>, Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, [[5]]]>>, Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 3>, [1, 2, 3, 4, [5]]>>, Ex
  • 풀이
    • 1차 시도 풀이는 1회 Flatten 하는 타입을 만들어두고, 횟수만큼 쌓기
      • 이 풀이는 마지막 케이스에서 너무 많은 재귀 깊이가 돌아 실패
  • 배운 점
    • 횟수를 셀 때, 누적 배열 길이를 사용해 셀 수 있다.
    • type Builder<T extends unknown[], Depth extends number, Acc extends never[] = []> = Acc['length'] extends Depth ? T : T extends [infer First, ...infer Rest] // First 가 배열인지 아닌지 확인 ? First extends any[] ? [...Builder<First, Depth, [...Acc, never]>, ...Builder<Rest, Depth, Acc>] : [First, ...Builder<Rest, Depth, Acc>] :T;
    • 해당 답도 다음과 같이 input T 자체가 너무 깊으면 인스턴스가 너무 깊다는 오류가 뜬다.
    • // @ts-expect-error: 형식 인스턴스화는 깊이가 매우 깊으며 무한할 수도 있습니다. type Test2 = FlattenDepth<[1, [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[2, [3, [4, [5]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], 19260817>;
    • 그러면 내가 적은 답은 Depth 에 따라 인스턴스 에러가 나고, 답지의 답은 T 의 배열 깊이에 따라 인스턴스 에러가나는 이유는 뭘까?
    • // 첫번째 코드의 재귀호출 조건은 Depth 숫자 여부에 따라서 type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest] ? First extends [...infer Inner] ? [...Inner, ...Flatten<Rest>] : [First, ...Flatten<Rest>] : T; type FlattenTest1 = Flatten<[1, 2, 3]>; type FlattenTest2 = Flatten<[1, [2], [[3]]]>; // 이 부분에서 Depth 숫자 여부에 따라 Builder 를 재귀호출 type Builder<T extends unknown[], Depth extends number, Acc extends any[] = []> = Acc['length'] extends Depth ? T : Builder<Flatten<T>, Depth, [...Acc, never]>; type FlattenDepth<T extends unknown[], Depth extends number = 1> = Builder<T, Depth>; type Test1 = FlattenDepth<[1, [[2]]]>; type Test2 = FlattenDepth<[1, 2, [3, 4], [[5]]], 2>;
      // 두번째 코드의 재귀호출 조건은 First 가 배열인지 아닌지 여부에 따라서 type Builder<T extends unknown[], Depth extends number, Acc extends never[] = []> = Acc['length'] extends Depth ? T : T extends [infer First, ...infer Rest] ? First extends any[] // 여기서 First 가 배열인지 여부에 따라 Builder 재귀호출 ? [...Builder<First, Depth, [...Acc, never]>, ...Builder<Rest, Depth, Acc>] : [First, ...Builder<Rest, Depth, Acc>] :T; type FlattenDepth<T extends unknown[], Depth extends number = 1> = Builder<T, Depth>; type Test1 = FlattenDepth<[1, [[2]]]>; type Test2 = FlattenDepth<[1, 2, [3, 4], [[5]]], 2>;

3326 - BEM style string

문제: The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.
/* _____________ 여기에 코드 입력 _____________ */ type DistributeArray<T extends string[]> = T extends [infer First extends string, ...infer Rest extends string[]] ? First | DistributeArray<Rest> : never; type DistributeArrayTest = DistributeArray<['a', 'b', 'c']>; type BEM<B extends string, E extends string[], M extends string[]> = E extends [] ? M extends [] ? // 둘다 [] `${B}` : // E 는 [], M 은 string[] `${B}--${DistributeArray<M>}` : M extends [] ? // M 은 [], E 는 string[] `${B}__${DistributeArray<E>}` : // 둘다 string[] `${B}__${DistributeArray<E>}--${DistributeArray<M>}`; type Test = BEM<'btn', ['price'], []>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<BEM<'btn', ['price'], []>, 'btn__price'>>, Expect<Equal<BEM<'btn', ['price'], ['warning', 'success']>, 'btn__price--warning' | 'btn__price--success' >>, Expect<Equal<BEM<'btn', [], ['small', 'medium', 'large']>, 'btn--small' | 'btn--medium' | 'btn--large' >>, ]
  • 풀이
    • 문자열 리터럴 튜플을 합집합으로 변경할 타입을 만든 후 조건에 따라 나눔
  • 배운 점
    • 해답을 보니 더 깔끔한 답들이 많았다.
    • type BEM<B extends string, E extends string[],M extends string[]> = `${B}${E extends [] ? '' : `__${E[number]}`}${M extends [] ? '' : `--${M[number]}`}`
    • 배열 E[number] 와 같은 경우에도 자동으로 분배되는 것 같다.

4179 - Flip

문제: Implement the type of just-flip-object. Examples:
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'}
/* _____________ 여기에 코드 입력 _____________ */ type Flip<T> = {[K in keyof T as T[K] extends PropertyKey ? T[K] : `${T[K]&boolean}`]: K} type Test = Flip<{ pi: 3.14, bool: true }> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect, NotEqual } from '@type-challenges/utils' type cases = [ Expect<Equal<{ a: 'pi' }, Flip<{ pi: 'a' }>>>, Expect<NotEqual<{ b: 'pi' }, Flip<{ pi: 'a' }>>>, Expect<Equal<{ 3.14: 'pi', true: 'bool' }, Flip<{ pi: 3.14, bool: true }>>>, Expect<Equal<{ val2: 'prop2', val: 'prop' }, Flip<{ prop: 'val', prop2: 'val2' }>>>, ]
  • 풀이
    • T 순회 돌 때 asT[K] 재정의 하여 뽑아온다.
    • true 라는 key 가 string 형태로 뽑히는게 아니라 boolean 으로 뽑히는 듯하여 조건 고려
  • 배운 점
    • 하지만, T[K]&Primitive 는 안 됨…
다음 글이 없습니다.