icon

메티의 블로그

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

17주차 타입챌린지 스터디

Tags
TypeScript
날짜
Apr 30, 2025
상태
공개

26401 - JSON Schema to TypeScript

Implement the generic type JSONSchema2TS which will return the TypeScript type corresponding to the given JSON schema.
Additional challenges to handle: * additionalProperties * oneOf, anyOf, allOf * minLength and maxLength
/* _____________ 여기에 코드 입력 _____________ */ type PrimitiveMapper<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : unknown; type ObjectMetadata = { type: string; properties?: { [key: string]: ObjectMetadata; } enum?: unknown[]; items?: ObjectMetadata; required?: string[]; } type JSONSchema2TS<T extends ObjectMetadata> = T extends { enum: infer Enum extends unknown[] } ? Enum[number] : T extends { type: infer TypeName } ? // object type TypeName extends 'object' ? T extends { properties: infer Object } ? T extends { required: infer Required extends string[] } ? Omit< { [K in keyof Object as K extends Required[number] ? K : never]: Object[K] extends ObjectMetadata ? JSONSchema2TS<Object[K]> : unknown } & { [K in keyof Object as K extends Required[number] ? never : K]?: Object[K] extends ObjectMetadata ? JSONSchema2TS<Object[K]> : unknown }, never> : { [K in keyof Object]?: Object[K] extends ObjectMetadata ? JSONSchema2TS<Object[K]> : unknown } : Record<string, unknown> : // array type TypeName extends 'array' ? T extends { items: infer ArrayInfo extends ObjectMetadata } ? JSONSchema2TS<ArrayInfo>[] : unknown[] : // primitive type PrimitiveMapper<TypeName> : never;

풀이

  1. 설계문제라고 생각했었음
  1. ObjectMetadata 타입을 통해 필요한 속성에 접근하려고 시도했으나 결국 필요 없었음. 그냥 확인용으로 유지
  1. 각 메타 데이터들이 존재 할 때 구분해야할 로직이 다르므로, 메타 데이터의 기준이 되는 속성 존재 여부로 처리 방법 다르게 나눔
  1. required 의 경우 OmitWrapper 로 사용해 해결(답안 참고)

배운점

type Primitives = { string: string; number: number; boolean: boolean; }; type HandlePrimitives<T, Type extends keyof Primitives> = T extends { enum: unknown[]; } ? T['enum'][number] : Primitives[Type]; type HandleObject<T> = T extends { properties: infer Properties extends Record<string, unknown>; } ? T extends { required: infer Required extends unknown[] } ? Omit< { [K in Required[number] & keyof Properties]: JSONSchema2TS< Properties[K] >; } & { [K in Exclude<keyof Properties, Required[number]>]?: JSONSchema2TS< Properties[K] >; }, never > : { [K in keyof Properties]?: JSONSchema2TS<Properties[K]>; } : Record<string, unknown>; type HandleArray<T> = T extends { items: infer Items } ? JSONSchema2TS<Items>[] : unknown[]; type JSONSchema2TS<T> = T extends { type: infer Type } ? Type extends keyof Primitives ? HandlePrimitives<T, Type> : Type extends 'object' ? HandleObject<T> : HandleArray<T> : never;
required 처리 시 & 연산과 Exclude 를 사용하는 것이 더 좋아보인다.

27133 - Square

Given a number, your type should return its square.
/* _____________ 여기에 코드 입력 _____________ */ type SquareCalc<N extends number, I extends never[] = [], J extends never[] = [], C extends never[] = []> = J['length'] extends N ? I['length'] extends N ? // 종료 C['length'] : // J 꽉 참, I 추가하며 C 채움 SquareCalc<N, [...I, never], J, [...C, ...J]> : // J 미종료 시 SquareCalc<N, I, [...J, never]>; type Abs<N extends number> = `${N}` extends `-${infer A extends number}` ? A : N; type Square<N extends number> = N extends 100 ? 10000 : N extends 101 ? 10201 : SquareCalc<Abs<N>>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Square<0>, 0>>, Expect<Equal<Square<1>, 1>>, Expect<Equal<Square<3>, 9>>, Expect<Equal<Square<20>, 400>>, Expect<Equal<Square<100>, 10000>>, Expect<Equal<Square<101>, 10201>>, // Negative numbers Expect<Equal<Square<-2>, 4>>, Expect<Equal<Square<-5>, 25>>, Expect<Equal<Square<-31>, 961>>, Expect<Equal<Square<-50>, 2500>>, ]

풀이

  1. IJ 두개의 인덱스를 돌며, JN 길이가 만족이 되면, I 를 기준으로 CJ 를 추가하는 로직을 사용해 추가하여 배열 길이를 리턴
  1. 100 이상의 엄청 큰 수는 문제에 대한 항의 표시로 하드코딩 함

배운점

  1. 배열의 길이가 10000 이 넘어가면 다음과 같은 에러 발생
type SquareCalc<N extends number, I extends never[] = [], J extends never[] = [], C extends never[] = []> = J['length'] extends N ? I['length'] extends N ? // 종료 C['length'] : // J 꽉 참, I 추가하며 C 채움 SquareCalc<N, [...I, never], J, [...C, ...J]> : // J 미종료 시 SquareCalc<N, I, [...J, never]>; type Abs<N extends number> = `${N}` extends `-${infer A extends number}` ? A : N; type Square<N extends number> = SquareCalc<Abs<N>>; // 9801 type Test = Square<99>; // ts-error: 형식이 너무 커서 표시할 수 없는 튜플 형식을 생성합니다. // any 가 됨 type Test2 = Square<100>; // ts-error: 형식이 너무 커서 표시할 수 없는 튜플 형식을 생성합니다. // number 가 됨 type Test3 = Square<101>;

27152 - Triangular number

Given a number N, find the Nth triangular number, i.e. 1 + 2 + 3 + ... + N
/* _____________ 여기에 코드 입력 _____________ */ // type Triangular<N extends number, I extends never[] = [], J extends never[] = [], C extends never[] = []> = // I['length'] extends N ? // C['length'] : // J['length'] extends [...I, never]['length'] ? // Triangular<N, [...I, never], J, [...C, ...J]>: // Triangular<N, I, [...J, never], C>; type Triangular<N extends number, I extends never[] = [], C extends never[] = []> = I['length'] extends N ? C['length'] : Triangular<N, [...I, never], [...C, ...[...I, never]]> ; type Test = Triangular<0>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Triangular<0>, 0>>, Expect<Equal<Triangular<1>, 1>>, Expect<Equal<Triangular<3>, 6>>, Expect<Equal<Triangular<10>, 55>>, Expect<Equal<Triangular<20>, 210>>, Expect<Equal<Triangular<55>, 1540>>, Expect<Equal<Triangular<100>, 5050>>, ]

풀이

  1. I 를 하나씩 늘려가며 CI 누적함

27862 - CartesianProduct

Given 2 sets (unions), return its Cartesian product in a set of tuples, e.g.
type Test = CartesianProduct<1 | 2, 'a' | 'b'> // [1, 'a'] | [2, 'a'] | [1, 'b'] | [2, 'b']
/* _____________ 여기에 코드 입력 _____________ */ type CartesianProduct<T, U> = T extends any ? U extends any ? [T, U] : never : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<CartesianProduct<1 | 2, 'a' | 'b'>, [2, 'a'] | [1, 'a'] | [2, 'b'] | [1, 'b']>>, Expect<Equal<CartesianProduct<1 | 2 | 3, 'a' | 'b' | 'c' >, [2, 'a'] | [1, 'a'] | [3, 'a'] | [2, 'b'] | [1, 'b'] | [3, 'b'] | [2, 'c'] | [1, 'c'] | [3, 'c']>>, Expect<Equal<CartesianProduct<1 | 2, 'a' | never>, [2, 'a'] | [1, 'a'] >>, Expect<Equal<CartesianProduct<'a', Function | string>, ['a', Function] | ['a', string]>>, ]

풀이

  1. TU 가 유니온으로 들어오기 때문에 조건부 연산을 통해 분배 조건부 연산으로 동작하도록 하고, 중첩하여 [T, U] 리턴

27932 - MergeAll

Merge variadic number of types into a new type. If the keys overlap, its values should be merged into an union.
type Foo = { a: 1; b: 2 } type Bar = { a: 2 } type Baz = { c: 3 } type Result = MergeAll<[Foo, Bar, Baz]> // expected to be { a: 1 | 2; b: 2; c: 3 }
/* _____________ 여기에 코드 입력 _____________ */ type MergeObject<T, U> = Omit< { [K in keyof T]: K extends keyof U ? T[K] | U[K] : T[K] } & { [K in keyof U]: K extends keyof T ? T[K] | U[K] : U[K] }, never>; type MergeAll<T extends unknown[]> = T extends [] ? {} : T extends [infer F, ...infer R] ? MergeObject<F, MergeAll<R>> : T; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MergeAll<[]>, {} >>, Expect<Equal<MergeAll<[{ a: 1 }]>, { a: 1 }>>, Expect<Equal< MergeAll<[{ a: string }, { a: string }]>, { a: string } > >, Expect<Equal< MergeAll<[{ }, { a: string }]>, { a: string } > >, Expect<Equal< MergeAll<[{ a: 1 }, { c: 2 }]>, { a: 1, c: 2 } > >, Expect<Equal< MergeAll<[{ a: 1, b: 2 }, { a: 2 }, { c: 3 }]>, { a: 1 | 2, b: 2, c: 3 } > >, Expect<Equal<MergeAll<[{ a: 1 }, { a: number }]>, { a: number }>>, Expect<Equal<MergeAll<[{ a: number }, { a: 1 }]>, { a: number }>>, Expect<Equal<MergeAll<[{ a: 1 | 2 }, { a: 1 | 3 }]>, { a: 1 | 2 | 3 }>>, ]

풀이

  1. 두개의 object 를 머지하는 타입을 우선 만듬.
  1. 두개 object 를 머지할 때는 T 의 key 인 KU 의 key 의 부분집합인지 확인하여 부분집합이면 유니온으로 두개 합치고, 아니면 하나만 하도록 한다. 그리고, 그 반대도 해주어야 한다. 그리고 후처리로 Omit 으로 감싸 & 연산을 하여 정확히 맞지 않는 타입을 정리해준다.
  1. 그리고, 이를 각 배열 순회 하며 재귀로 쌓아감

배운점

type MergeAll< XS extends object[], U = XS[number], Keys extends PropertyKey = U extends U ? keyof U : never > = { [K in Keys]: U extends U ? U[K & keyof U] : never }
이런 멋진 답도 있었다.
type MergeAll< XS extends object[], U = XS[number], Keys extends PropertyKey = U extends U ? keyof U : never > = { [K in Keys]: U extends U ? U[K & keyof U] : never } type A = MergeAll<[{a: 1}, {b: 2}]> // becomes type A = { [K in 'a']: {a: 1}['a' & ('a' | 'b')] | {b: 2}['a' & ('a' | 'b')] [K in 'b']: {a: 1}['b' & ('a' | 'b')] | {b: 2}['b' & ('a' | 'b')] } // becomes type A = { [K in 'a']: {a: 1}['a'] | {b: 2}[never] [K in 'b']: {a: 1}[never] | {b: 2}['b'] } // becomes type A = { [K in 'a']: 1 | never; [K in 'b']: never | 2; } // becomes type A = { a: 1 b: 2 }

27958 - CheckRepeatedTuple

Implement type CheckRepeatedChars<T> which will return whether type T contains duplicated member
type CheckRepeatedTuple<[1, 2, 3]> // false type CheckRepeatedTuple<[1, 2, 1]> // true
/* _____________ 여기에 코드 입력 _____________ */ //type IsEqual<X, Y> = (<T>() => T extends Y ? 1 : 2) extends (<T>() => T extends X ? 1 : 2) ? true : false; type Has<T extends unknown[], G> = T extends [infer F, ...infer R] ? IsEqual<F, G> extends true ? true : Has<R, G> : false; type CheckRepeatedTuple<T extends unknown[]> = T extends [infer F, ...infer R] ? Has<R, F> extends true ? true : CheckRepeatedTuple<R> : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<CheckRepeatedTuple<[number, number, string, boolean]>, true>>, Expect<Equal<CheckRepeatedTuple<[number, string]>, false>>, Expect<Equal<CheckRepeatedTuple<[1, 2, 3]>, false>>, Expect<Equal<CheckRepeatedTuple<[1, 2, 1]>, true>>, Expect<Equal<CheckRepeatedTuple<[]>, false>>, Expect<Equal<CheckRepeatedTuple<string[]>, false>>, Expect<Equal<CheckRepeatedTuple<[number, 1, string, '1', boolean, true, false, unknown, any]>, false>>, Expect<Equal<CheckRepeatedTuple<[never, any, never]>, true>>, ]

풀이

  1. F 와 나머지 배열에서 찾아 확인하는 방법 사용, 어차피 한번 지나친 것은 다시 볼 필요 없어서 이렇게 함
다음 글이 없습니다.

연관 포스트