icon

메티의 블로그

타입챌린지 스터디 풀이 모음
타입챌린지 스터디 풀이 모음

타입챌린지 스터디 풀이 모음

Tags
TypeScript
날짜
May 14, 2025
상태
공개

개요

타입챌린지 스터디 easy, medium 풀이를 한 곳에 통합 합니다. 장장 3개월간 하루 평균 한 문제씩 풀이 하며 15개의 포스트를 작성했고, 기존은 주차마다 포스트를 나눴으나, 이제 스터디를 종료하여 하나의 포스트로 통합하였습니다.

4주차 타입챌린지 스터디

15 Last of Array

문제: 배열 T를 사용하고 마지막 요소를 반환하는 제네릭 Last<T>를 구현합니다.
/* _____________ 여기에 코드 입력 _____________ */ type Last<T extends any[]> = T extends [...infer _, infer Tail] ? Tail : never /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Last<[]>, never>>, Expect<Equal<Last<[2]>, 2>>, Expect<Equal<Last<[3, 2, 1]>, 1>>, Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>>, ]

배운점

  • infer 를 배열 내에서도 사용할 수 있다.

16 Pop

문제: 배열 T를 사용해 마지막 요소를 제외한 배열을 반환하는 제네릭 Pop<T>를 구현합니다.
/* _____________ 여기에 코드 입력 _____________ */ type Pop<T extends any[]> = T['length'] extends 0 ? [] : T extends [...infer Rest, infer _] ? Rest : never /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Pop<[3, 2, 1]>, [3, 2]>>, Expect<Equal<Pop<['a', 'b', 'c', 'd']>, ['a', 'b', 'c']>>, Expect<Equal<Pop<[]>, []>>, ]

풀이

  • 빈 배열 확인을 위해 T[’length’] extends 0 사용

배운점

  • infer 를 rest 연산과 같이 사용할 수 있다

20 Promise.all

문제: Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.
/* _____________ 여기에 코드 입력 _____________ */ // declare function PromiseAll<T extends any[]>(values: T): Promise<{[index in keyof T]: T[index] extends readonly [...infer Const] ? Const : T[index] extends Promise<infer Inner> ? Inner : T[index]}>; // Promise 를 꺼내는 것은 Awaited 로 declare function PromiseAll<T extends any[]>(values: T): Promise<{[index in keyof T]: T[index] extends readonly [...infer Const] ? Const : Awaited<T[index]>}>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' const promiseAllTest1 = PromiseAll([1, 2, 3] as const) const promiseAllTest2 = PromiseAll([1, 2, Promise.resolve(3)] as const) const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]) const promiseAllTest4 = PromiseAll<Array<number | Promise<number>>>([1, 2, 3]) const promiseAllTest5 = PromiseAll([1, 2, 3]) type cases = [ Expect<Equal<typeof promiseAllTest1, Promise<[1, 2, 3]>>>, Expect<Equal<typeof promiseAllTest2, Promise<[1, 2, number]>>>, Expect<Equal<typeof promiseAllTest3, Promise<[number, number, number]>>>, Expect<Equal<typeof promiseAllTest4, Promise<number[]>>>, Expect<Equal<typeof promiseAllTest5, Promise<[number, number, number]>>>, ]

풀이

  • as const 처리를 위해 readonly 인 경우 그 값을 그대로 사용
  • 아니면 Awaited 로 꺼냄, Promise 내부에 Promise 가 또 있을 수 있으므로 recursive 하게 구현 (Awaited 는 recursive 하게 구현 되어있음)

배운점

interface PromiseLike<T> { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>; } /** * Represents the completion of an asynchronous operation */ interface Promise<T> { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>; } /** * Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`. */ type Awaited<T> = T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode T extends object & { then(onfulfilled: infer F, ...args: infer _): any; } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument Awaited<V> : // recursively unwrap the value never : // the argument to `then` was not callable T; // non-object or non-thenable
  • infer 를 제네릭 선언 안 쪽에서도 사용할 수 있다.
  • infer 를 함수 args 타입 선언에서도 사용할 수 있다.

62 Type Lookup

문제: 이 챌린지에서는 유니온 타입 Cat | Dog에서 공통으로 사용하는 type 필드를 기준으로 해당하는 타입을 얻고자 합니다. 다시 말해서, 다음 예시에서는 LookUp<Cat | Dog, 'dog'>으로 Dog 타입을, LookUp<Cat | Dog, 'cat'>으로 Cat 타입을 얻을 수 있습니다.
/* _____________ 여기에 코드 입력 _____________ */ // type LookUp<U extends {type: string}, T> = U['type'] extends T ? U : never; type LookUp<U, T> = U extends { type: T } ? U : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Cat { type: 'cat' breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal' } interface Dog { type: 'dog' breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer' color: 'brown' | 'white' | 'black' } type Animal = Cat | Dog type cases = [ Expect<Equal<LookUp<Animal, 'dog'>, Dog>>, Expect<Equal<LookUp<Animal, 'cat'>, Cat>>, ]

배운점

  • 직접 접근하게 되면 리터럴 타입으로 (U[’type’])찍힌다.

106 Trim Left

문제: 정확한 문자열 타입이고 시작 부분의 공백이 제거된 새 문자열을 반환하는 TrimLeft<T>를 구현하십시오.
/* _____________ 여기에 코드 입력 _____________ */ // infer 로 재지정 기능 활용 type TrimLeft<S extends string> = S extends `${" "|"\n"|"\t"}${infer Rest}` ? TrimLeft<Rest> : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TrimLeft<'str'>, 'str'>>, Expect<Equal<TrimLeft<' str'>, 'str'>>, Expect<Equal<TrimLeft<' str'>, 'str'>>, Expect<Equal<TrimLeft<' str '>, 'str '>>, Expect<Equal<TrimLeft<' \n\t foo bar '>, 'foo bar '>>, Expect<Equal<TrimLeft<''>, ''>>, Expect<Equal<TrimLeft<' \n\t'>, ''>>, ]

배운점

  • infer 를 통해서 문자열 타입 재정의를 할 수 있다.

108 Trim

문제: 정확한 문자열 타입이고 양쪽 끝의 공백이 제거된 새 문자열을 반환하는 Trim<T>를 구현하십시오.
/* _____________ 여기에 코드 입력 _____________ */ type TrimLeft<S extends string> = S extends `${" "|"\n"|"\t"}${infer Rest}` ? TrimLeft<Rest> : S; type TrimRight<S extends string> = S extends `${infer Rest}${" "|"\n"|"\t"}` ? TrimRight<Rest> : S; type Trim<S extends string> = TrimLeft<TrimRight<S>>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Trim<'str'>, 'str'>>, Expect<Equal<Trim<' str'>, 'str'>>, Expect<Equal<Trim<' str'>, 'str'>>, Expect<Equal<Trim<'str '>, 'str'>>, Expect<Equal<Trim<' str '>, 'str'>>, Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>, Expect<Equal<Trim<''>, ''>>, Expect<Equal<Trim<' \n\t '>, ''>>, ]

5주차 타입챌린지 스터디

119 Capitalize

문제: 문자열의 첫 글자만 대문자로 바꾸고 나머지는 그대로 놔두는 Capitalize<T>를 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type MyCapitalize<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MyCapitalize<'foobar'>, 'Foobar'>>, Expect<Equal<MyCapitalize<'FOOBAR'>, 'FOOBAR'>>, Expect<Equal<MyCapitalize<'foo bar'>, 'Foo bar'>>, Expect<Equal<MyCapitalize<''>, ''>>, Expect<Equal<MyCapitalize<'a'>, 'A'>>, Expect<Equal<MyCapitalize<'b'>, 'B'>>, Expect<Equal<MyCapitalize<'c'>, 'C'>>, Expect<Equal<MyCapitalize<'d'>, 'D'>>, Expect<Equal<MyCapitalize<'e'>, 'E'>>, Expect<Equal<MyCapitalize<'f'>, 'F'>>, Expect<Equal<MyCapitalize<'g'>, 'G'>>, Expect<Equal<MyCapitalize<'h'>, 'H'>>, Expect<Equal<MyCapitalize<'i'>, 'I'>>, Expect<Equal<MyCapitalize<'j'>, 'J'>>, Expect<Equal<MyCapitalize<'k'>, 'K'>>, Expect<Equal<MyCapitalize<'l'>, 'L'>>, Expect<Equal<MyCapitalize<'m'>, 'M'>>, Expect<Equal<MyCapitalize<'n'>, 'N'>>, Expect<Equal<MyCapitalize<'o'>, 'O'>>, Expect<Equal<MyCapitalize<'p'>, 'P'>>, Expect<Equal<MyCapitalize<'q'>, 'Q'>>, Expect<Equal<MyCapitalize<'r'>, 'R'>>, Expect<Equal<MyCapitalize<'s'>, 'S'>>, Expect<Equal<MyCapitalize<'t'>, 'T'>>, Expect<Equal<MyCapitalize<'u'>, 'U'>>, Expect<Equal<MyCapitalize<'v'>, 'V'>>, Expect<Equal<MyCapitalize<'w'>, 'W'>>, Expect<Equal<MyCapitalize<'x'>, 'X'>>, Expect<Equal<MyCapitalize<'y'>, 'Y'>>, Expect<Equal<MyCapitalize<'z'>, 'Z'>>, ]

배운점

  • Uppercase 는 빌트인 되어있는 타입스크립트 유틸리티타입 이다.
    • /** * Convert string literal type to uppercase */ type Uppercase<S extends string> = intrinsic; //본질적인
  • intrinsic 키워드를 보고, 타입스크립트 엔진 내 빌트인 로직을 적용할 것으로 보임

116 Replace

문제: 문자열 S에서 From를 찾아 한 번만 To로 교체하는 Replace<S, From, To>를 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type Replace<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer I}${From}${infer Rest}` ? `${I}${To}${Rest}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Replace<'foobar', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<Replace<'foobarbar', 'bar', 'foo'>, 'foofoobar'>>, Expect<Equal<Replace<'foobarbar', '', 'foo'>, 'foobarbar'>>, Expect<Equal<Replace<'foobarbar', 'bar', ''>, 'foobar'>>, Expect<Equal<Replace<'foobarbar', 'bra', 'foo'>, 'foobarbar'>>, Expect<Equal<Replace<'', '', ''>, ''>>, ]

풀이

  • From 을 넣고 찾은 후, 앞의 문자열을 I, 뒤의 문자열을 Rest 로 나누어 보낸다.
  • 이때는 가장 먼저 나온 From 문자열 값을 기준으로 한다.

배운점

  • 템플릿 리터럴 타입에서 infer 키워드를 사용하면 패턴 매칭 ex)정규표현식 처럼 이루어 진다.

119 Replace All

문제: 주어진 문자열 S에서 부분 문자열 From을 찾아 모두 To로 교체하는 제네릭 ReplaceAll<S, From, To>을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type ReplaceAll<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer I}${From}${infer Rest}` ? `${I}${To}${ReplaceAll<Rest, From, To>}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ReplaceAll<'foobar', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<ReplaceAll<'foobar', 'bag', 'foo'>, 'foobar'>>, Expect<Equal<ReplaceAll<'foobarbar', 'bar', 'foo'>, 'foofoofoo'>>, Expect<Equal<ReplaceAll<'t y p e s', ' ', ''>, 'types'>>, Expect<Equal<ReplaceAll<'foobarbar', '', 'foo'>, 'foobarbar'>>, Expect<Equal<ReplaceAll<'barfoo', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<ReplaceAll<'foobarfoobar', 'ob', 'b'>, 'fobarfobar'>>, Expect<Equal<ReplaceAll<'foboorfoboar', 'bo', 'b'>, 'foborfobar'>>, Expect<Equal<ReplaceAll<'', '', ''>, ''>>, ]

풀이

  • 위의 문제는 첫 From 을 패턴 매칭해 변경해 주었으므로, Rest 를 재귀를 통한 나머지 문자열을 같은 방식으로 해결하면 된다.

191 Append Argument

문제: 함수 타입 Fn과 어떤 타입 A가 주어질 때 Fn의 인수와 A마지막 인수로 받는 Fn과 동일한 함수 유형인 G를 생성하세요.
/* _____________ 여기에 코드 입력 _____________ */ // type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer Args) => infer Ret ? (x: A, ...args: Args) => Ret : never; type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer Args) => infer Ret ? (...args: [...Args, A]) => Ret : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Case1 = AppendArgument<(a: number, b: string) => number, boolean> type Result1 = (a: number, b: string, x: boolean) => number type Case2 = AppendArgument<() => void, undefined> type Result2 = (x: undefined) => void type cases = [ Expect<Equal<Case1, Result1>>, Expect<Equal<Case2, Result2>>, // @ts-expect-error AppendArgument<unknown, undefined>, ]

배운점

  • 타입을 […Args, A] 와 같이 합칠 수 있다.
  • extends function 할 때 (…args: any[]): => any 이런식으로 화살표 함수로 하는게 좋다.

296 Permutation

문제: 주어진 유니언 타입을 순열 배열로 바꾸는 Permutation 타입을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type Permutation<T, E = T> = [T] extends [never] ? [] : T extends E ? [T, ...Permutation<Exclude<E, T>>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Permutation<'A'>, ['A']>>, Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>, Expect<Equal<Permutation<never>, []>>, ]

배운 점

  • 제네릭을 그대로 조건부 타입을 통해 연산을 할 시(naked type parameter), 분배 조건부 타입으로 분배된다. 이때, T 에 never 가 들어오면, 분배될 집합 자체가 없어, 연산 자체가 동작하지 않아 never 가 된다.
    • type Test<T> = T extends never ? : 1 : 0; type Test1 = Test<never>; //never type Test2 = never extends never ? : 1 : 0; // 1
      그래서 never 를 조건부 타입 연산에 명확하게 필터링 하고 싶다면 다음과 같이 타입 재정의를 해주면 된다.
      type Test<T> = [T] extends [never] ? : 1 : 0;
  • 제네릭에서 재귀 시 새로운 변수를 만들고 싶다면, 기본값을 넣은 두번째 매개변수를 추가해도 좋다.
  • 타입스크립트에서 유니온 타입은 순서가 없다. ‘A’ | ‘B’‘B’ | ‘A’ 는 같다. 분배 조건부 타입 연산 시 내부적인 순서는 있다.
    • type T1 = string | never; type T2 = string; type Test = Equal<T1, T2>; // true /** - 문자열 리터럴 유니온 정렬은 알파벳 순서 - 숫자 리터럴 유니온은 오름차순 - boolean 은 false - never 는 아예 없는 취급 */
 

풀이

  • T extends E ? [T, …Permutation<Exclude<E, T>>] : [] 는 T 가 제네릭 그대로 사용했으므로 분배 조건부 타입으로 분배 된다.
type Permutation<T, E = T> = [T] extends [never] ? [] : T extends E ? [T, ...Permutation<Exclude<E, T>>] : []; type Test = Permutation<'A' | 'B'>; // ('A' extends 'A' | 'B' ? ['A', ...Permutation<Exclude<'A' | 'B', 'A'>] : []) | ('B' extends 'A' | 'B' ? ['B', ...Permutation<Exclude<'A' | 'B', 'B'>] : []) // Permutation<'B'> = ['B'] 이므로 풀어내면 ['A' | 'B'] | ['B' | 'A']

298 Length of String

문제: String#length처럼 동작하는 문자열 리터럴의 길이를 구하세요.
// type LengthOfString<S extends string> = S['length'] type LengthOfString<S extends string, Count extends unknown[] = []> = S extends `${infer First}${infer Rest}` ? LengthOfString<Rest, [...Count, First]> : Count['length']; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<LengthOfString<'kumiko'>, 6>>, Expect<Equal<LengthOfString<'reina'>, 5>>, Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>, ]

풀이

  • string 타입을 받아, 그 길이에 맞는 배열을 만들고, 그 배열의 length 를 return
  • 패턴매칭 첫번째는 첫번째 문자 / 나머지 이므로 다음과 같은 재귀를 통해 Count 배열을 쌓아감

배운 점

  • string 타입의 S[’length’]number 이고, Array 타입의 S[’length’] 는 리터럴 타입(숫자)이다.

6주차 타입챌린지 스터디

459 Flattern

문제: 주어진 배열을 플랫한 배열 타입으로 바꾸는 Flatten 타입을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ // type Flatten<T extends unknown[], C = []> = {[index in keyof T]: T[index] extends [infer Inner] ? (Inner extends unknown[] ? Flatten<Inner, [C, ...Inner]> : Inner) : T[index]} type Flatten<T extends unknown[]> = T extends [infer First, ...infer Rest] ? First extends unknown[] ? [...Flatten<First>, ...Flatten<Rest>] : [First, ...Flatten<Rest>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Flatten<[]>, []>>, Expect<Equal<Flatten<[1, 2, 3, 4]>, [1, 2, 3, 4]>>, Expect<Equal<Flatten<[1, [2]]>, [1, 2]>>, Expect<Equal<Flatten<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, 5]>>, Expect<Equal<Flatten<[{ foo: 'bar', 2: 10 }, 'foobar']>, [{ foo: 'bar', 2: 10 }, 'foobar']>>, ] // @ts-expect-error type error = Flatten<'1'>

배운점

  • 배열의 경우 infer 로 꺼내지 않고, …Arr 형태로 꺼내도 된다.

527 Append to object

문제: 주어진 인터페이스에 새로운 필드를 추가한 object 타입을 구현하세요. 이 타입은 세 개의 인자를 받습니다.
/* _____________ 여기에 코드 입력 _____________ */ // type AppendToObject<T, U extends keyof any, V> = T & { [K in U]: V } // type AppendToObject<T, U, V> = {[index in keyof (T & U)]: index extends keyof T ? T[index] : V} type AppendToObject<T, U extends keyof any, V> = {[index in keyof T | U]: index extends keyof T ? T[index] : V} type Test = AppendToObject<test1, 'home', boolean> type A = { a: string; } type B = { b: string; } type C = A & B; type D = { a: string; b: string; } /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type test1 = { key: 'cat' value: 'green' } type testExpect1 = { key: 'cat' value: 'green' home: boolean } type test2 = { key: 'dog' | undefined value: 'white' sun: true } type testExpect2 = { key: 'dog' | undefined value: 'white' sun: true home: 1 } type test3 = { key: 'cow' value: 'yellow' sun: false } type testExpect3 = { key: 'cow' value: 'yellow' sun: false moon: false | undefined } type cases = [ Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>, Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>, Expect<Equal<AppendToObject<test3, 'moon', false | undefined>, testExpect3>>, // @ts-expect-error Expect<Equal<C, D>> ]

배운점

type A = { a: string; } type B = { b: string; } type C = A & B; type D = { a: string; b: string; } import type { Equal, Expect } from '@type-challenges/utils' // false Expect<Equal<C, D>>
A & B 와 A 와 B 에 있는 속성이 동시에 들어있는 것은 타입스크립트 컴파일러가 다르다고 판단한다.
type AppendToObject<T, U, V> = {[index in keyof (T & U)]: index extends keyof T ? T[index] : V}
이 풀이는 object 가 가지고 있는 기본적인 속성들을 모두 포함한다.
U 는 이미 keyof anyextends 이므로, keyof TU 의 합집합을 순회함
 

529 Absolute

문제: number, string, 혹은 bigint을 받는 Absolute 타입을 만드세요. 출력은 양수 문자열이어야 합니다.
/* _____________ 여기에 코드 입력 _____________ */ type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer Rest}` ? `${Rest}` : `${T}`; type Test = Absolute<-5> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Absolute<0>, '0'>>, Expect<Equal<Absolute<-0>, '0'>>, Expect<Equal<Absolute<10>, '10'>>, Expect<Equal<Absolute<-5>, '5'>>, Expect<Equal<Absolute<'0'>, '0'>>, Expect<Equal<Absolute<'-0'>, '0'>>, Expect<Equal<Absolute<'10'>, '10'>>, Expect<Equal<Absolute<'-5'>, '5'>>, Expect<Equal<Absolute<-1_000_000n>, '1000000'>>, Expect<Equal<Absolute<9_999n>, '9999'>>, ]

배운점

  • `${T}` extends `${A}${B}` 형태로 앞 타입을 템플릿 리터럴 형태로도 선처리 할 수 있다.
 

531 String to union

문제: 문자열 인수를 입력받는 String to Union 유형을 구현하세요. 출력은 입력 문자열의 Union type이어야 합니다.
/* _____________ 여기에 코드 입력 _____________ */ type StringToUnion<T extends string, C = never> = T extends `${infer First}${infer Rest}` ? StringToUnion<Rest, C | First> : C; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<StringToUnion<''>, never>>, Expect<Equal<StringToUnion<'t'>, 't'>>, Expect<Equal<StringToUnion<'hello'>, 'h' | 'e' | 'l' | 'l' | 'o'>>, Expect<Equal<StringToUnion<'coronavirus'>, 'c' | 'o' | 'r' | 'o' | 'n' | 'a' | 'v' | 'i' | 'r' | 'u' | 's'>>, ]

배운점

  • 유니온 타입은 내부 처리로 인해 둘이 같다.
type Case = Expect<Equal<'h' | 'e' | 'l' | 'o', 'h' | 'e' | 'l' | 'l' | 'o'>>,
 

599 Merge

문제: 두개의 타입을 새로운 타입으로 병합하세요. 두번째 타입의 Key가 첫번째 타입을 덮어씁니다(재정의합니다)
/* _____________ 여기에 코드 입력 _____________ */ type Merge<F, S> = {[index in (keyof F | keyof S)]: index extends keyof S ? S[index] : index extends keyof F ? F[index] : never} /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Foo = { a: number b: string } type Bar = { b: number c: boolean } type cases = [ Expect<Equal<Merge<Foo, Bar>, { a: number b: number c: boolean }>>, ]

풀이

  • 두 타입의 키를 합쳐 순회 돌면서 첫번째 타입을 두번째 타입의 key 가 덮어 쓰기 때문에 S 를 우선 return

612 KebabCase

문제: camelCase나 PascalCase를 kebab-case 문자열로 수정하세요.
/* _____________ 여기에 코드 입력 _____________ */ type KebabCase<S extends string> = S extends `${infer First}${infer Rest}` ? Rest extends Uncapitalize<Rest> ? `${Uncapitalize<First>}${KebabCase<Rest>}` : `${Uncapitalize<First>}-${KebabCase<Rest>}` : S; type Test = KebabCase<'ABC'>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<KebabCase<'FooBarBaz'>, 'foo-bar-baz'>>, Expect<Equal<KebabCase<'fooBarBaz'>, 'foo-bar-baz'>>, Expect<Equal<KebabCase<'foo-bar'>, 'foo-bar'>>, Expect<Equal<KebabCase<'foo_bar'>, 'foo_bar'>>, Expect<Equal<KebabCase<'Foo-Bar'>, 'foo--bar'>>, Expect<Equal<KebabCase<'ABC'>, 'a-b-c'>>, Expect<Equal<KebabCase<'-'>, '-'>>, Expect<Equal<KebabCase<''>, ''>>, Expect<Equal<KebabCase<'😎'>, '😎'>>, ]

배운점

  • 처음에는 Lowercase 유틸리티 타입을 사용해서 풀어보려고 했으나 쉽지 않았음
  • Uncapitalize 유틸리티 타입은 문자열의 첫 문자를 소문자로 변경해줌, intrinsic 타입으로 내부구현 되어있음

7주차 타입챌린지 스터디

645 Diff

문제: O & O1의 차이점인 객체를 가져옵니다
/* _____________ 여기에 코드 입력 _____________ */ // type Diff<O, O1> = {[index in (keyof O | keyof O1)]: index extends keyof O ? index extends keyof O1 ? never : O[index] : index extends keyof O1 ? O1[index] : never} type Diff<O, O1> = {[index in Exclude<(keyof O | keyof O1), (keyof O & keyof O1)>]: index extends keyof O ? index extends keyof O1 ? never : O[index] : index extends keyof O1 ? O1[index] : never} /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Foo = { name: string age: string } type Bar = { name: string age: string gender: number } type Coo = { name: string gender: number } type cases = [ Expect<Equal<Diff<Foo, Bar>, { gender: number }>>, Expect<Equal<Diff<Bar, Foo>, { gender: number }>>, Expect<Equal<Diff<Foo, Coo>, { age: string, gender: number }>>, Expect<Equal<Diff<Coo, Foo>, { age: string, gender: number }>>, ]

배운점

  • key 순회 할 때 아예 빼는 것과 공집합을 주는 것은 다르다.
  • extends 로 명확하게 타입 상속을 지정해주지 않으면, 타입스크립트는 해당 타입을 명시하지 못 한다.

949 Any of

문제: Python 의 any of 함수를 구현하세요. (어떤 배열 요소의 값중 하나라도 truthy 한 값이 있는지 판별하는 함수)
/* _____________ 여기에 코드 입력 _____________ */ type isFalsy<T> = T extends 0 | "" | false | undefined | null ? true : // T extends [infer Inner] ? Inner extends never ? true : false : T extends unknown[] ? T['length'] extends 0 ? true : false : keyof T extends never ? true : false; type AnyOf<T extends readonly any[]> = T extends [infer First, ...infer Rest] ? isFalsy<First> extends true ? AnyOf<Rest> : true : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<AnyOf<[1, 'test', true, [1], { name: 'test' }, { 1: 'test' }]>, true>>, Expect<Equal<AnyOf<[1, '', false, [], {}]>, true>>, Expect<Equal<AnyOf<[0, 'test', false, [], {}]>, true>>, Expect<Equal<AnyOf<[0, '', true, [], {}]>, true>>, Expect<Equal<AnyOf<[0, '', false, [1], {}]>, true>>, Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }]>, true>>, Expect<Equal<AnyOf<[0, '', false, [], { 1: 'test' }]>, true>>, Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }, { 1: 'test' }]>, true>>, Expect<Equal<AnyOf<[0, '', false, [], {}, undefined, null]>, false>>, Expect<Equal<AnyOf<[]>, false>>, ]

배운점

  • falsy 한 값을 판별하기 위해 사용하는 방법으로 다음을 코드 안 방법을 사용한다. 특히 배열은 length 0 으로 한다. 내부 배열을 꺼내서 never 는 안 먹힘
    • // false type Test = [] extends [infer Inner] ? true : false;
      애초에 [] extends [infer Inner] ? true : false 는 false 임
  • {}nullundefined 값을 제외한 나머지 모든 타입과 조건부 타입 연산 시 true 를 반환 함
  • T[number] 로 순회 할 수 있다.
 

1024 Is never

문제: input type으로 T를 받는 IsNever type을 구현하세요. 만약 T의 유형이 never으로 확인되면 true를 반환하고 아니면 false를 반환합니다
/* _____________ 여기에 코드 입력 _____________ */ type IsNever<T> = [T] extends [never] ? true : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsNever<never>, true>>, Expect<Equal<IsNever<never | string>, false>>, Expect<Equal<IsNever<''>, false>>, Expect<Equal<IsNever<undefined>, false>>, Expect<Equal<IsNever<null>, false>>, Expect<Equal<IsNever<[]>, false>>, Expect<Equal<IsNever<{}>, false>>, ]

배운점

이전에도 한적 있지만, Tnever 로 들어오면 분배 조건부 타입 연산시 never 로 들어가 아예 조건부 타입 연산 자체가 누락되어버린다. (분배 조건부 타입의 연산은 우선 유니언 타입의 각 요소들을 기준으로 조건부 타입 연산을 실행하고, 후에 유니언으로 합치는 방식인데, never 가 제네릭으로 들어올 시 유니언으로 합칠때 never 이므로 누락되는 것 처럼 보임) 이를 피하기 위해서는 타입을 재가공하여 비교한다.

1097 Is union

문제: T를 입력으로 받고, TUnion 유형으로 확인되는지 여부를 반환하는 IsUnion을 구현하세요
/* _____________ 여기에 코드 입력 _____________ */ // type IsUnion<T, C extends unknown[] = []> = [T] extends [never] ? false : T extends infer First | infer Rest ? IsUnion<Rest, [C, First]> : C['length'] extends 1 ? false : true; type Test = IsUnion<'a' | 'b'> type ToArray<T> = [T] extends [any] ? T[] : never; // (string | number)[] type DistributeToArray<T> = T extends any ? T[] : never; // string[] | number[] type IsUnion<T> = [T] extends [never] ? false : ToArray<T> extends DistributeToArray<T> ? false : true; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsUnion<string>, false>>, Expect<Equal<IsUnion<string | number>, true>>, Expect<Equal<IsUnion<'a' | 'b' | 'c' | 'd'>, true>>, Expect<Equal<IsUnion<undefined | null | void | ''>, true>>, Expect<Equal<IsUnion<{ a: string } | { a: number }>, true>>, Expect<Equal<IsUnion<{ a: string | number }>, false>>, Expect<Equal<IsUnion<[string | number]>, false>>, // Cases where T resolves to a non-union type. Expect<Equal<IsUnion<string | never>, false>>, Expect<Equal<IsUnion<string | unknown>, false>>, Expect<Equal<IsUnion<string | any>, false>>, Expect<Equal<IsUnion<string | 'a'>, false>>, Expect<Equal<IsUnion<never>, false>>, ]

풀이

  • 분배 조건부 타입과 조건부 타입 변형을 비교해 같으면 유니온 타입이 아니라는 뜻

배운점

  • 조건부 타입 infer[infer A | infer B] 는 의도대로 유니언을 나누는 동작을 하지 않는다.
  • 조건부 타입에 유니온 타입이 들어오면, 분배되어 들어온다.

1130 Replace key

문제: Union type의 key를 대체하는 ReplaceKeys를 구현하세요. 만약 일부 유형에 해당 key가 존재하지 않는다면 대체하지 않습니다. 타입은 세 개의 인자를 받습니다.
/* _____________ 여기에 코드 입력 _____________ */ type ReplaceKeys<U, T, Y> = {[index in keyof U]: index extends T ? index extends keyof Y ? Y[index] : never : U[index]} /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type NodeA = { type: 'A' name: string flag: number } type NodeB = { type: 'B' id: number flag: number } type NodeC = { type: 'C' name: string flag: number } type ReplacedNodeA = { type: 'A' name: number flag: string } type ReplacedNodeB = { type: 'B' id: number flag: string } type ReplacedNodeC = { type: 'C' name: number flag: string } type NoNameNodeA = { type: 'A' flag: number name: never } type NoNameNodeC = { type: 'C' flag: number name: never } type Nodes = NodeA | NodeB | NodeC type ReplacedNodes = ReplacedNodeA | ReplacedNodeB | ReplacedNodeC type NodesNoName = NoNameNodeA | NoNameNodeC | NodeB type cases = [ Expect<Equal<ReplaceKeys<Nodes, 'name' | 'flag', { name: number, flag: string }>, ReplacedNodes>>, Expect<Equal<ReplaceKeys<Nodes, 'name', { aa: number }>, NodesNoName>>, ]

풀이

  • 구현 문제, 특정 key 가 유니언 타입으로 들어오고, 그에 맞는 가이드 객체가 존재하면 그 속성대로 변경해줌

배운점

  • 조건부 타입이 꼭 아니더라도, 제네릭이 유니온으로 들어오면 기본적으로 분배되어 동작하는듯
    • 아님

1367 Remove index signature

문제: 객체 유형에서 인덱스 시그니처를 제외하는 RemoveIndexSignature<T>를 구현하세요
/* _____________ 여기에 코드 입력 _____________ */ // type RemoveIndexSignature<T> = {[index in Exclude<keyof T, keyof {[key: keyof any]: any}>]: T[index]} type RemoveIndexSignature<T> = {[index in keyof T as (string extends index ? never : number extends index ? never : symbol extends index ? never : index)]: T[index]} type Test = RemoveIndexSignature<Foo> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Foo = { [key: string]: any foo(): void } type Bar = { [key: number]: any bar(): void 0: string } const foobar = Symbol('foobar') type FooBar = { [key: symbol]: any [foobar](): void } type Baz = { bar(): void baz: string } type cases = [ Expect<Equal<RemoveIndexSignature<Foo>, { foo(): void }>>, Expect<Equal<RemoveIndexSignature<Bar>, { bar(): void, 0: string }>>, Expect<Equal<RemoveIndexSignature<FooBar>, { [foobar](): void }>>, Expect<Equal<RemoveIndexSignature<Baz>, { bar(): void, baz: string }>>, ]

배운점

  • 인덱스 시그니처는 런타임에 타입을 추가하고 싶을 때, 이를 추가 할 수 있도록 제약을 풀어주는 역할을 함
  • as 타입 단언을 통해 타입을 재정의 해주면, 타입스크립트는 재정의된 타입 기준으로 검사한다.

8주차 타입챌린지 스터디

1978 - Percentage Parser

문제: PercentageParser을 구현하세요. /^(\\+|\\-)?(\\d*)?(\\%)?$/ 정규식에 따라 T를 일치시키고 3개의 일치 요소를 얻습니다.
/* _____________ 여기에 코드 입력 _____________ */ type UnsignedPercentageParser<A extends string> = A extends `${infer Num}%` ? [Num, "%"] : [A, ""]; type PercentageParser<A extends string> = A extends `${infer Sign extends "+" | "-"}${infer Rest}` ? [Sign,...UnsignedPercentageParser<Rest>] : ["", ...UnsignedPercentageParser<A>] /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Case0 = ['', '', ''] type Case1 = ['+', '', ''] type Case2 = ['+', '1', ''] type Case3 = ['+', '100', ''] type Case4 = ['+', '100', '%'] type Case5 = ['', '100', '%'] type Case6 = ['-', '100', '%'] type Case7 = ['-', '100', ''] type Case8 = ['-', '1', ''] type Case9 = ['', '', '%'] type Case10 = ['', '1', ''] type Case11 = ['', '100', ''] type cases = [ Expect<Equal<PercentageParser<''>, Case0>>, Expect<Equal<PercentageParser<'+'>, Case1>>, Expect<Equal<PercentageParser<'+1'>, Case2>>, Expect<Equal<PercentageParser<'+100'>, Case3>>, Expect<Equal<PercentageParser<'+100%'>, Case4>>, Expect<Equal<PercentageParser<'100%'>, Case5>>, Expect<Equal<PercentageParser<'-100%'>, Case6>>, Expect<Equal<PercentageParser<'-100'>, Case7>>, Expect<Equal<PercentageParser<'-1'>, Case8>>, Expect<Equal<PercentageParser<'%'>, Case9>>, Expect<Equal<PercentageParser<'1'>, Case10>>, Expect<Equal<PercentageParser<'100'>, Case11>>, ]

풀이

  • 한번에 하기 파싱하기 어려워 나누어 해결

2070 - Drop Char

문제: Drop a specified char from a string.
/* _____________ 여기에 코드 입력 _____________ */ type DropChar<S, C> = S extends `${infer First}${infer Rest}` ? First extends C ? DropChar<Rest, C> : `${First}${DropChar<Rest, C>}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ // @ts-expect-error Expect<Equal<DropChar<'butter fly!', ''>, 'butterfly!'>>, Expect<Equal<DropChar<'butter fly!', ' '>, 'butterfly!'>>, Expect<Equal<DropChar<'butter fly!', '!'>, 'butter fly'>>, Expect<Equal<DropChar<' butter fly! ', ' '>, 'butterfly!'>>, Expect<Equal<DropChar<' b u t t e r f l y ! ', ' '>, 'butterfly!'>>, Expect<Equal<DropChar<' b u t t e r f l y ! ', 'b'>, ' u t t e r f l y ! '>>, Expect<Equal<DropChar<' b u t t e r f l y ! ', 't'>, ' b u e r f l y ! '>>, ]

풀이

  1. 문자열 순차적으로 순회하며 C와 조건부 타입 연산
  1. true 면 제외 후 연산, false 면 포함

2257 - MinusOne

문제: Given a number (always positive) as a type. Your type should return the number decreased by one.
/* _____________ 여기에 코드 입력 _____________ */ // 숫자 빼기 맵퍼 type RotateDigitString<T extends string> = T extends "0" ? "9" : T extends "1" ? "0" : T extends "2" ? "1" : T extends "3" ? "2" : T extends "4" ? "3" : T extends "5" ? "4" : T extends "6" ? "5" : T extends "7" ? "6" : T extends "8" ? "7" : T extends "9" ? "8" : never; type RotateDigitStringTest = RotateDigitString<"0">; // "9" // 전체를 변경해야함 type StringToNumber<T extends string> = T extends `${infer Num extends number}` ? Num : never; type StringToNumberTest1 = StringToNumber<"00"> // number type StringToNumberTest2 = StringToNumber<"1"> // 1 type StringToNumberTest3 = StringToNumber<"001"> // number type SplitString<T extends string, C extends string[] = []> = T extends `${infer First}${infer Rest}` ? SplitString<Rest, [...C, First]> : C; type SplitStringTest1 = SplitString<"1000"> // ["1", "0", "0", "0"] type SplitStringTest2 = SplitString<"1"> // ["1"] type SubstractOne< T extends string[], Borrow extends boolean = true, C extends string[] = [] > = T extends [...infer Rest extends string[], infer Last extends string] ? Borrow extends true ? Last extends "0" ? SubstractOne<Rest, true, [RotateDigitString<Last>, ...C]> : SubstractOne<Rest, false, [RotateDigitString<Last>, ...C]> : SubstractOne<Rest, false, [Last, ...C]> : C; type SubstractOneTest1 = SubstractOne<["1", "1"]> // ["1", "0"] type SubstractOneTest2 = SubstractOne<["5", "5"]> // ["5", "4"] type SubstringOneTest3 = SubstractOne<["1", "0", "0"]> // ["0", "9", "9"] type SubstractOneTest4 = SubstractOne<["1"]> // ["0"] type JoinString<T extends string[], C extends string = ""> = T extends [infer First extends string, ...infer Rest extends string[]] ? JoinString<Rest,`${C}${First}`> : C; type JoinStringTest1 = JoinString<["9", "8", "0"]> // "089" type JoinStringTest2 = JoinString<["1"]> // "1" type RemoveZeroPad<T extends string> = T extends `0${infer Rest}` ? Rest extends "" ? "0" : RemoveZeroPad<Rest> : T; type RemoveZeroPadTest1 = RemoveZeroPad<"0101"> // "101" type RemoveZeroPadTest2 = RemoveZeroPad<"0"> // "0" type MinusOne<T extends number> = StringToNumber<RemoveZeroPad<JoinString<SubstractOne<SplitString<`${T}`>>>>> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MinusOne<1>, 0>>, Expect<Equal<MinusOne<55>, 54>>, Expect<Equal<MinusOne<3>, 2>>, Expect<Equal<MinusOne<100>, 99>>, Expect<Equal<MinusOne<1101>, 1100>>, Expect<Equal<MinusOne<9_007_199_254_740_992>, 9_007_199_254_740_991>>, ]

풀이

  • 여러 과정을 추가해야 풀린다는 사실을 확인 후 풀이
  • RotateDigitString 을 통해 숫자 1 을 빼는 과정 매핑
  • string 형태로 되어있는 것을 number 로 변경해주는 것이 필요 (마지막에 숫자 리터럴 타입으로 리턴해야하기 때문)
  • SplitString 을 통해 각 문자열 리터럴을 쪼개서 배열 형태로 (문자열 리터럴 그대로 사용하면 앞에서 밖에 읽지 못하기 때문)
  • SubstractOne 을 통해 배열로 변환된 숫자 배열에서 1을 뺌, Borrow 변수를 통해 0 빼기 여부 체크
  • JoinString 을 통해 Split 한 문자열 합침
  • RemoveZeroPad 를 통해 앞의 0 문자 제거

배운점

  • StringToNumber
// 전체를 변경해야함 type StringToNumber<T extends string> = T extends `${infer Num extends number}` ? Num : never; type StringToNumberTest1 = StringToNumber<"00"> // number type StringToNumberTest2 = StringToNumber<"1"> // 1 type StringToNumberTest3 = StringToNumber<"001"> // number type StringToNumberTest4 = StringToNumber<"-1"> // -1 type StringToNumberTest5 = StringToNumber<"0.5"> // 0.5
00 과 같이 적절한 숫자가 아닌 경우 타입을 number 로, 아니면 숫자 리터럴을 return

2595 - PickByType

문제: From T, pick a set of properties whose type are assignable to U.
/* _____________ 여기에 코드 입력 _____________ */ // type PickByType<T, U> = {[index in keyof T as keyof (T[index] extends U ? T[index] : never)]: T[index]}; type PickByType<T, U> = {[K in keyof T as (T[K] extends U ? K : never)]: T[K]}; type Test = PickByType<Model, boolean> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string count: number isReadonly: boolean isEnable: boolean } type cases = [ Expect<Equal<PickByType<Model, boolean>, { isReadonly: boolean, isEnable: boolean }>>, Expect<Equal<PickByType<Model, string>, { name: string }>>, Expect<Equal<PickByType<Model, number>, { count: number }>>, ]

배운점

  • K 자체를 타입 연산의 결과로 사용할 수 있다.

2688 - StartsWith

문제: Implement StartsWith<T, U> which takes two exact string types and returns whether T starts with U
/* _____________ 여기에 코드 입력 _____________ */ type StartsWith<T extends string, U extends string> = T extends `${U}${infer _}` ? true : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<StartsWith<'abc', 'ac'>, false>>, Expect<Equal<StartsWith<'abc', 'ab'>, true>>, Expect<Equal<StartsWith<'abc', 'abc'>, true>>, Expect<Equal<StartsWith<'abc', 'abcd'>, false>>, Expect<Equal<StartsWith<'abc', ''>, true>>, Expect<Equal<StartsWith<'abc', ' '>, false>>, Expect<Equal<StartsWith<'', ''>, true>>, ]

배운점

  • 타입스크립트 문자열 리터럴 타입을 조건부 연산이 매칭 할 때는 패턴 매칭처럼 동작하기 때문에 간단하게 해결 할 수 있다.
 

2693 - EndsWith

문제: Implement `EndsWith<T, U>` which takes two exact string types and returns whether `T` ends with `U`
/* _____________ 여기에 코드 입력 _____________ */ type EndsWith<T extends string, U extends string> = T extends `${infer _}${U}` ? true : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<EndsWith<'abc', 'bc'>, true>>, Expect<Equal<EndsWith<'abc', 'abc'>, true>>, Expect<Equal<EndsWith<'abc', 'd'>, false>>, Expect<Equal<EndsWith<'abc', 'ac'>, false>>, Expect<Equal<EndsWith<'abc', ''>, true>>, Expect<Equal<EndsWith<'abc', ' '>, false>>, ]

9주차 타입챌린지 스터디

2757 - PartialByKeys

문제: 두 개의 타입 인수 TK를 사용하는 PartialByKeys<T, K>를 구성하세요. K는 옵셔널하며 T의 프로퍼티로 이루어진 유니언 타입을 지정할 수 있습니다. K를 제공하지 않는다면 Partial<T>와 같이 모든 프로퍼티를 옵셔널하게 만들어야 합니다.
/* _____________ 여기에 코드 입력 _____________ */ // type PartialByKeys<T, K> = {[Index in keyof T as (Index extends K ? Index : never)]+?: T[Index]} & {[Index in keyof T as (Index extends K ? never : Index)]: T[Index]} type IterateObject<T> = {[K in keyof T]: T[K]} type PartialByKeys<T, K extends keyof T = keyof T> = IterateObject<{[Index in keyof T as (Index extends K ? Index : never)]+?: T[Index]} & {[Index in keyof T as (Index extends K ? never : Index)]: T[Index]}> type Test = keyof User /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface User { name: string age: number address: string } interface UserPartialName { name?: string age: number address: string } interface UserPartialNameAndAge { name?: string age?: number address: string } type cases = [ Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>, Expect<Equal<PartialByKeys<User, 'name' | 'age'>, UserPartialNameAndAge>>, Expect<Equal<PartialByKeys<User>, Partial<User>>>, // @ts-expect-error Expect<Equal<PartialByKeys<User, 'name' | 'unknown'>, UserPartialName>> ]

풀이

  • & 인터섹션 연산은 의도와 다르게 풀이 되므로 Iterate 해주는 타입으로 래핑하는 것이 필요했다.

2759 - RequiredByKeys

문제: Implement a generic RequiredByKeys<T, K> which takes two type argument T and K. K specify the set of properties of T that should set to be required. When K is not provided, it should make all properties required just like the normal Required<T>.
/* _____________ 여기에 코드 입력 _____________ */ type IterateObject<T> = {[K in keyof T]: T[K]} type RequiredByKeys<T, K extends keyof T = keyof T> = IterateObject<{[Index in keyof T as Index extends K ? Index : never]-?: T[Index]} & {[Index in Exclude<keyof T, K>]?: T[Index]}> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface User { name?: string age?: number address?: string } interface UserRequiredName { name: string age?: number address?: string } interface UserRequiredNameAndAge { name: string age: number address?: string } type cases = [ Expect<Equal<RequiredByKeys<User, 'name'>, UserRequiredName>>, Expect<Equal<RequiredByKeys<User, 'name' | 'age'>, UserRequiredNameAndAge>>, Expect<Equal<RequiredByKeys<User>, Required<User>>>, // @ts-expect-error Expect<Equal<RequiredByKeys<User, 'name' | 'unknown'>, UserRequiredName>>, ]

배운점

  • -? 를 통해 optional 을 제거할 수 있다.
 

2793 - Mutable

문제: Implement the generic Mutable<T> which makes all properties in T mutable (not readonly).
/* _____________ 여기에 코드 입력 _____________ */ type Mutable<T extends object> = {-readonly [K in keyof T]: T[K]} /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Todo1 { title: string description: string completed: boolean meta: { author: string } } type List = [1, 2, 3] type cases = [ Expect<Equal<Mutable<Readonly<Todo1>>, Todo1>>, Expect<Equal<Mutable<Readonly<List>>, List>>, ] type errors = [ // @ts-expect-error Mutable<'string'>, // @ts-expect-error Mutable<0>, ]

배운점

  • - readonly 로 뺄 수 있음

2852 - OmitByType

문제: From T, pick a set of properties whose type are not assignable to U.
/* _____________ 여기에 코드 입력 _____________ */ type OmitByType<T, U> = {[K in keyof T as T[K] extends U ? never : K]: T[K]} type Test = OmitByType<Model, boolean> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string count: number isReadonly: boolean isEnable: boolean } type cases = [ Expect<Equal<OmitByType<Model, boolean>, { name: string, count: number }>>, Expect<Equal<OmitByType<Model, string>, { count: number, isReadonly: boolean, isEnable: boolean }>>, Expect<Equal<OmitByType<Model, number>, { name: string, isReadonly: boolean, isEnable: boolean }>>, ]

배운점

  • K 자체를 타입 연산의 결과로 사용할 수 있다.
 

2946 - ObjectEntries

문제: Implement the type version of Object.entries
/* _____________ 여기에 코드 입력 _____________ */ type InnerDistributer<T, K extends keyof T = keyof T> = K extends any ? [K, T[K]] : never; export type ObjectEntries<T> = InnerDistributer<T> type Dental = ObjectEntries<Partial<Model>> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string age: number locations: string[] | null } type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null] type cases = [ Expect<Equal<ObjectEntries<Model>, ModelEntries>>, Expect<Equal<ObjectEntries<Partial<Model>>, ModelEntries>>, Expect<Equal<ObjectEntries<{ key?: undefined }>, ['key', undefined]>>, Expect<Equal<ObjectEntries<{ key: undefined }>, ['key', undefined]>>, Expect<Equal<ObjectEntries<{ key: string | undefined }>, ['key', string | undefined]>>, ]

풀이

  • 배열의 유니온 형태로 return 이 되어야 하므로 분배 조건부 연산이 필요하다.
  • 각 key 와 그에 맞는 타입이 같이 배열에 들어가야하므로 새로운 제네릭 인자 K 를 넣고 기본값으로 keyof T 를 추가한다.
  • K 자리에 뭔가 들어가면 안 되므로, 래퍼 타입을 만든다.
 

3062 - Shift

문제: Implement the type version of Array.shift
/* _____________ 여기에 코드 입력 _____________ */ type Shift<T extends unknown[]> = T extends [infer _, ...infer Rest] ? Rest : T; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ // @ts-expect-error Shift<unknown>, Expect<Equal<Shift<[]>, []>>, Expect<Equal<Shift<[1]>, []>>, Expect<Equal<Shift<[3, 2, 1]>, [2, 1]>>, Expect<Equal<Shift<['a', 'b', 'c', 'd']>, ['b', 'c', 'd']>>, ]

10주차 타입챌린지 스터디

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 배열을 뒤집어 풀이함

배운점

{[K in First extends string ? First : never]: C}
키 순회 시, 항상 keyof T 만 가능한 것은 아니다. 다른 타입을 infer 로 가져와서 순회 돌게 할 수 있다.
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;
횟수를 셀 때, 누적 배열 길이를 사용해 셀 수 있다.
// @ts-expect-error: 형식 인스턴스화는 깊이가 매우 깊으며 무한할 수도 있습니다. type Test2 = FlattenDepth<[1, [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[2, [3, [4, [5]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]], 19260817>;
해당 답도 다음과 같이 input T 자체가 너무 깊으면 인스턴스가 너무 깊다는 오류가 뜬다.
그러면 내가 적은 답은 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] 와 같은 경우에도 자동으로 분배되는 것 같다.
 

3376 - Inorder Traversal

문제: Implement the type version of binary tree inorder traversal.
/* _____________ 여기에 코드 입력 _____________ */ interface TreeNode { val: number left: TreeNode | null right: TreeNode | null } // type Traversal<C extends TreeNode | null, Acc extends number[] = []> = // C extends TreeNode ? // C['left'] extends TreeNode ? Traversal<C['left'] ,[...Acc, C['val']]> : // C['right'] extends TreeNode ? Traversal<C['right'],[...Acc, C['val']]> : // [...Acc, C['val']]: // Acc; // type Test = Traversal<typeof tree1> type InorderTraversal<T extends TreeNode | null> = T extends TreeNode ? [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' const tree1 = { val: 1, left: null, right: { val: 2, left: { val: 3, left: null, right: null, }, right: null, }, } as const const tree2 = { val: 1, left: null, right: null, } as const const tree3 = { val: 1, left: { val: 2, left: null, right: null, }, right: null, } as const const tree4 = { val: 1, left: null, right: { val: 2, left: null, right: null, }, } as const type cases = [ Expect<Equal<InorderTraversal<null>, []>>, Expect<Equal<InorderTraversal<typeof tree1>, [1, 3, 2]>>, Expect<Equal<InorderTraversal<typeof tree2>, [1]>>, Expect<Equal<InorderTraversal<typeof tree3>, [2, 1]>>, Expect<Equal<InorderTraversal<typeof tree4>, [1, 2]>>, ]

풀이

  • 중위 순회 시 순서로 return 배열을 셋으로 쪼개 받으면 된다.

배운점

  • 처음에 누적 배열을 사용하려고 했으나… 답안을 보니 불가능 한 듯

11주차 타입챌린지 스터디

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'}
/* _____________ 여기에 코드 입력 _____________ */ // T[K] extends Primitive ? T[K] : never; 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 는 안 됨…

4182 - Fibonacci Sequence

문제: Implement a generic Fibonacci<T> that takes a number T and returns its corresponding [Fibonacci number]
/* _____________ 여기에 코드 입력 _____________ */ // 작은 숫자 덧셈 타입 type SmallAdder<A extends number, B extends number, AccA extends never[] = [], AccB extends never[] = []> = AccA['length'] extends A ? AccB['length'] extends B ? [...AccA, ...AccB]['length']: SmallAdder<A, B, AccA, [...AccB, never]>: SmallAdder<A, B, [...AccA, never], AccB>; type SmallAdderTest = SmallAdder<9, 5>; // 작은 숫자 1 빼기 타입 type SmallMinusOne<A extends number, Acc extends never[] = []> = Acc['length'] extends A ? Acc extends [infer _, ...infer Rest] ? Rest['length'] : never : SmallMinusOne<A, [...Acc, never]>; type SmallMinusOneTest = SmallMinusOne<5>; // 피보나치 타입 type Fibonacci<T extends number> = T extends 1 ? 1: T extends 2 ? 1: SmallAdder<Fibonacci<SmallMinusOne<T>>, Fibonacci<SmallMinusOne<SmallMinusOne<T>>>>; type FibonacciTest = Fibonacci<16>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Fibonacci<1>, 1>>, Expect<Equal<Fibonacci<2>, 1>>, Expect<Equal<Fibonacci<3>, 2>>, Expect<Equal<Fibonacci<8>, 21>>, Expect<Equal<Fibonacci<16>, 987>> ]

풀이

  • 작은 숫자 더하기 타입을 T[’length’] 로 만들어서 사용

배운점

  • 최대 depth 는 16

4260 - AllCombinations

문제:Implement type AllCombinations<S> that return all combinations of strings which use characters from S at most once.
/* _____________ 여기에 코드 입력 _____________ */ type String2Union<S extends String> = S extends `${infer L}${infer R}` ? L | String2Union<R> : S; type AllCombinations<S extends string> = _AllCombinations<String2Union<S>>; type Combination<A extends string, B extends string> = | A | B | `${A}${B}` | `${B}${A}`; type _AllCombinations<A extends string, B extends string = A> = A extends A ? Combination<A, _AllCombinations<Exclude<B, A>>> : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<AllCombinations<''>, ''>>, Expect<Equal<AllCombinations<'A'>, '' | 'A'>>, Expect<Equal<AllCombinations<'AB'>, '' | 'A' | 'B' | 'AB' | 'BA'>>, Expect<Equal<AllCombinations<'ABC'>, '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'>>, Expect<Equal<AllCombinations<'ABCD'>, '' | 'A' | 'B' | 'C' | 'D' | 'AB' | 'AC' | 'AD' | 'BA' | 'BC' | 'BD' | 'CA' | 'CB' | 'CD' | 'DA' | 'DB' | 'DC' | 'ABC' | 'ABD' | 'ACB' | 'ACD' | 'ADB' | 'ADC' | 'BAC' | 'BAD' | 'BCA' | 'BCD' | 'BDA' | 'BDC' | 'CAB' | 'CAD' | 'CBA' | 'CBD' | 'CDA' | 'CDB' | 'DAB' | 'DAC' | 'DBA' | 'DBC' | 'DCA' | 'DCB' | 'ABCD' | 'ABDC' | 'ACBD' | 'ACDB' | 'ADBC' | 'ADCB' | 'BACD' | 'BADC' | 'BCAD' | 'BCDA' | 'BDAC' | 'BDCA' | 'CABD' | 'CADB' | 'CBAD' | 'CBDA' | 'CDAB' | 'CDBA' | 'DABC' | 'DACB' | 'DBAC' | 'DBCA' | 'DCAB' | 'DCBA'>>, ]

풀이

  • 유니온으로 변경하는 타입, 컴비네이션 생성 타입을 통해 컴비네이션을 구현
 

4425 - Greater Than

문제: In This Challenge, You should implement a type GreaterThan<T, U> like T > U Negative numbers do not need to be considered.
/* _____________ 여기에 코드 입력 _____________ */ // length 를 통해 작은 -1 을 만듬 type SmallMinusOne<A extends number, Acc extends never[] = []> = Acc['length'] extends A ? Acc extends [infer _, ...infer Rest] ? Rest['length'] : never : SmallMinusOne<A, [...Acc, never]>; // 양쪽 다 1씩 빼가며 0 만들기, 먼저 0 이 되었을 때 플래그 리턴 type SmallGreaterThan<T extends number, U extends number> = T extends 0 ? // T == 0 U extends 0 ? // 둘 다 0 false : // T 는 0, U 는 0이 아닐 때 false: // T != 0 U extends 0 ? // U 만 0 true: // 둘 다 0 이 아닐 때 SmallGreaterThan<SmallMinusOne<T>, SmallMinusOne<U>>; type SmallGreaterThanTest = SmallGreaterThan<5,3>; type NumberToTuple<T extends number> = `${T}` extends `${infer First extends number}${infer Rest extends number}` ? [First, ...NumberToTuple<Rest>] : [T]; type NumberToTupleTest = NumberToTuple<234>; // tuple 인 타입 순서대로 크기 검사하기 type GreaterThanSquentially<T extends number[], U extends number[]> = // 자리수 검사 SmallGreaterThan<T['length'], U['length']> extends true ? true : // 배열 검사 T extends [infer TFirst extends number, ...infer TRest extends number[]] ? U extends [infer UFirst extends number, ...infer URest extends number[]] ? // 첫번째 자리 검사 SmallGreaterThan<TFirst, UFirst> extends true ? true: // 아니면 다음 자리 검사 GreaterThanSquentially<TRest, URest>: // 여기로는 빠질리 없어야한다. never: // 자리수 짧음 false; type GreaterThanSquentiallyTest = GreaterThanSquentially<[1,2,4], [1,2,3]>; // 0. 두 수를 비교하기 좋게 전처리하기 위해 숫자를 튜플로 변경 // 1. 자리수 우선 비교 // 2. 자리수가 같으면, 앞자리부터 비교 type GreaterThan<T extends number, U extends number> = GreaterThanSquentially<NumberToTuple<T>, NumberToTuple<U>>; type Test = GreaterThan<4,5>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<GreaterThan<1, 0>, true>>, Expect<Equal<GreaterThan<5, 4>, true>>, Expect<Equal<GreaterThan<4, 5>, false>>, Expect<Equal<GreaterThan<0, 0>, false>>, Expect<Equal<GreaterThan<10, 9>, true>>, Expect<Equal<GreaterThan<20, 20>, false>>, Expect<Equal<GreaterThan<10, 100>, false>>, Expect<Equal<GreaterThan<111, 11>, true>>, Expect<Equal<GreaterThan<1234567891011, 1234567891010>, true>>, ]

풀이

  • 두 수를 비교하기 좋게 전처리하기 위해 숫자를 튜플로 변경
  • 자리수를 비교해서 T 가 자리수가 더 많으면 true
  • 자리수가 같으면 앞자리수 부터 비교함
  • 작은 수 비교는 양 수를 1씩 빼가면서 0 에 먼저 도달하는 경우 리턴
  • -1 은 이번엔 한자리씩만 뺄 것이므로 length 로 구현

4471 - Zip

문제: In This Challenge, You should implement a type Zip<T, U>, T and U must be Tuple
/* _____________ 여기에 코드 입력 _____________ */ type Zip<T extends unknown[], U extends unknown[]> = T extends [infer TFirst, ...infer TRest] ? U extends [infer UFirst, ...infer URest] ? [[TFirst, UFirst], ...Zip<TRest, URest>] : []: []; type Test = Zip<[1,2], [true, false]> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Zip<[], []>, []>>, Expect<Equal<Zip<[1, 2], [true, false]>, [[1, true], [2, false]]>>, Expect<Equal<Zip<[1, 2, 3], ['1', '2']>, [[1, '1'], [2, '2']]>>, Expect<Equal<Zip<[], [1, 2, 3]>, []>>, Expect<Equal<Zip<[[1, 2]], [3]>, [[[1, 2], 3]]>>, ]

배운점

  • 두 배열을 한번에 순회하는 방법은 조건부 연산 중첩으로.. 이미 앞에서 써먹었음

4484 - IsTuple

문제: Implement a type IsTuple, which takes an input type T and returns whether T is tuple type.
/* _____________ 여기에 코드 입력 _____________ */ type IsTuple<T> = [T] extends [never] ? false : T extends readonly any[] ? number extends T['length'] ? false : true :false type Test = IsTuple<never> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsTuple<[]>, true>>, Expect<Equal<IsTuple<[number]>, true>>, Expect<Equal<IsTuple<readonly [1]>, true>>, Expect<Equal<IsTuple<{ length: 1 }>, false>>, Expect<Equal<IsTuple<number[]>, false>>, Expect<Equal<IsTuple<never>, false>>, ]

배운점

number extends T['length'] ? false : true
튜플은 배열의 길이를 명확하게 가진 것이므로 해당 조건문으로 구별할 수 있다.

12주차 타입챌린지 스터디

4499 - Chunk

문제: Do you know lodash? Chunk is a very useful function in it, now let's implement it. Chunk<T, N> accepts two required type parameters, the T must be a tuple, and the N must be an integer >=1
type exp1 = Chunk<[1, 2, 3], 2> // expected to be [[1, 2], [3]] type exp2 = Chunk<[1, 2, 3], 4> // expected to be [[1, 2, 3]] type exp3 = Chunk<[1, 2, 3], 1> // expected to be [[1], [2], [3]]
/* _____________ 여기에 코드 입력 _____________ */ // type SimpleAdder<N extends number, C extends never[] = []> = C['length'] extends N ? [...C, never]['length'] : SimpleAdder<N, [...C, never]>; // /** // * // * N: 커널 크기 // * Curr: 중간 반복 인덱스 // * CurrAcc: 중간 반복 누적 배열 // * I: 전체 인덱스 // * Total: 최종 리턴 타입 // */ // type SliceArray<T extends unknown[], // N extends number, // Curr extends number = 0, // CurrAcc extends unknown[] = [], // I extends number = 0, // Total extends unknown[] = [] // > = // // 인덱스 순회 // I extends T['length'] ? // // 종료 // Total : // // I 가 아직 안 끝났을 때, 반복 인덱스 확인 // Curr extends N ? // // 반복 인덱스가 다 도달했을 경우 // SliceArray<T, N, 0, [], I, [...Total, CurrAcc]> : // // 반복 인덱스가 다 도달하지 않았을 때, T 순회 // T extends [infer First, ...infer Rest] ? // SliceArray<Rest, N, SimpleAdder<Curr>, [...CurrAcc, First], SimpleAdder<I>, Total> : // Total; // type Chunk<T extends unknown[], N extends number> = SliceArray<T, N>; type Chunk< T extends unknown[], N extends number, Acc extends unknown[] = [] > = T extends [infer First, ...infer Rest] ? Acc['length'] extends N ? [Acc, ...Chunk<T, N>] : Chunk<Rest, N, [...Acc, First]> : Acc extends [] ? [] : [Acc]; type Test = Chunk<[1, 2, 3], 2>;

풀이

  • 1차 시도로 인덱스 순회해가며 커널 크기에 따라 답을 모아가는 방식으로 해결하려고 시도
  • 시도 중 T 순회 마무리 로직 작성이 안 되어 포기 (vscode 컴파일러가 엄청 느리게 동작)
  • 2차 시도로 답을 참고해 T 순회를 돌며 누적 배열을 커널 크기에 따라 맞추고, 아닐 시에는 누적 배열을 늘려감, 마무리 시 누적 배열 빈배열 처리

배운점

  • [Acc, …Chunk<T, N>] 에서의 T, N 은 절대 원본 T, N 이 들어갈 일이 없다. 재귀적 사고방식에 익숙해져야한다.

4518 - Fill

문제:Fill, a common JavaScript function, now let us implement it with types. Fill<T, N, Start?, End?>, as you can see,Fill accepts four types of parameters, of which T and N are required parameters, and Start and End are optional parameters. The requirements for these parameters are: T must be a tuple, N can be any type of value, Start and End must be integers greater than or equal to 0.
/* _____________ 여기에 코드 입력 _____________ */ type SimpleAdder<N extends number, C extends never[] = []> = C['length'] extends N ? [...C, never]['length'] : SimpleAdder<N, [...C, never]>; type SimpleAdderTest = SimpleAdder<1>; // type SmallMinusOne<A extends number, Acc extends never[] = []> = Acc['length'] extends A ? // Acc extends [infer _, ...infer Rest] ? // Rest['length'] : // never : // SmallMinusOne<A, [...Acc, never]>; // type SmallGreaterThan<T extends number, U extends number> = T extends 0 ? // // T == 0 // U extends 0 ? // // 둘 다 0 // false : // // T 는 0, U 는 0이 아닐 때 // false: // // T != 0 // U extends 0 ? // // U 만 0 // true: // // 둘 다 0 이 아닐 때 // SmallGreaterThan<SmallMinusOne<T>, SmallMinusOne<U>>; // type ChangeTupleByIndex<T extends unknown[], N, I> = {[K in keyof T]: K extends `${I & number}` ? N : T[K]} // type ChangeTupleByIndexTest = ChangeTupleByIndex<[1,2,3], 0, 1> // type Fill< // T extends unknown[], // N, // Start extends number = 0, // End extends number = T['length'], // > = SmallGreaterThan<T['length'], Start> extends true ? T : // Start extends End ? // T: // Fill<ChangeTupleByIndex<T, N, Start>, N, SimpleAdder<Start>, End>; type SmallMinusOne<A extends number, Acc extends never[] = []> = Acc['length'] extends A ? Acc extends [infer _, ...infer Rest] ? Rest['length'] : never : SmallMinusOne<A, [...Acc, never]>; type SmallGreaterThan<T extends number, U extends number> = T extends 0 ? // T == 0 U extends 0 ? // 둘 다 0 false : // T 는 0, U 는 0이 아닐 때 false: // T != 0 U extends 0 ? // U 만 0 true: // 둘 다 0 이 아닐 때 SmallGreaterThan<SmallMinusOne<T>, SmallMinusOne<U>>; type Fill< T extends unknown[], N, Start extends number = 0, End extends number = T['length'], I extends number = 0 > = I extends End ? T: T extends [infer First, ...infer Rest] ? SmallGreaterThan<Start, I> extends true ? [First, ...Fill<Rest, N, Start, End, SimpleAdder<I>>]: [N, ...Fill<Rest, N, Start, End, SimpleAdder<I>>]: T; type Test = Fill<[1, 2, 3], true, 0, 1>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Fill<[], 0>, []>>, Expect<Equal<Fill<[], 0, 0, 3>, []>>, Expect<Equal<Fill<[1, 2, 3], 0, 0, 0>, [1, 2, 3]>>, Expect<Equal<Fill<[1, 2, 3], 0, 2, 2>, [1, 2, 3]>>, Expect<Equal<Fill<[1, 2, 3], 0>, [0, 0, 0]>>, Expect<Equal<Fill<[1, 2, 3], true>, [true, true, true]>>, Expect<Equal<Fill<[1, 2, 3], true, 0, 1>, [true, 2, 3]>>, Expect<Equal<Fill<[1, 2, 3], true, 1, 3>, [1, true, true]>>, Expect<Equal<Fill<[1, 2, 3], true, 10, 0>, [1, 2, 3]>>, Expect<Equal<Fill<[1, 2, 3], true, 10, 20>, [1, 2, 3]>>, Expect<Equal<Fill<[1, 2, 3], true, 0, 10>, [true, true, true]>>, ]

풀이

  • 첫번째 방식은 하나의 인덱스를 찾아 바꾸는 타입을 만들고, Start 부터 End 까지 찾아 바꾸는 방식 하려고했으나, 첫번째 문제 처럼 너무 느리고, 특정 테스트케이스에서 너무 깊은 인스턴스 에러가 났음
  • 두번째 방식은 답안을 조금 참고하여 인덱스를 돌며 Start 보다 큰 경우에는 N 으로 변경하는 방식

배운점

type Debug = SmallGreaterThan<Start, I> extends true ? [First, ...Fill<Rest, N, Start, End, SimpleAdder<I>>]: [N, ...Fill<Rest, N, Start, End, SimpleAdder<I>>]: T;
특정 배열을 변경하고 순회하는 것은 아래와 같이 할 수도 있다.

4803 - Trim Right

문제: 정확한 문자열 타입이고 끝부분의 공백이 제거된 새 문자열을 반환하는 Trim<T>를 구현하십시오.
/* _____________ 여기에 코드 입력 _____________ */ type TrimRight<S extends string> = S extends `${infer Str extends string}${" "|"\n"|"\t"}` ? TrimRight<Str> : S; type Test = TrimRight<'str '> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TrimRight<'str'>, 'str'>>, Expect<Equal<TrimRight<'str '>, 'str'>>, Expect<Equal<TrimRight<'str '>, 'str'>>, Expect<Equal<TrimRight<' str '>, ' str'>>, Expect<Equal<TrimRight<' foo bar \n\t '>, ' foo bar'>>, Expect<Equal<TrimRight<''>, ''>>, Expect<Equal<TrimRight<'\n\t '>, ''>>, ]

배운점

  • 이전에 했던 문제, 템플릿 문자열을 할 때는 유니온도 된다는 점 상기
 

5117 - Without

문제: Implement the type version of Lodash.without, Without<T, U> takes an Array T, number or array U and returns an Array without the elements of U.
/* _____________ 여기에 코드 입력 _____________ */ type ToUnion<T extends unknown[] | unknown> = T extends [infer First, ...infer Rest] ? First | ToUnion<Rest> : T; type Without<T extends unknown[], U> = T extends [infer Some, ...infer Rest] ? Some extends ToUnion<U> ? Without<Rest, U> : [Some, ...Without<Rest, U>] : []; type Test = Without<[1, 2, 4, 1, 5], [1, 2]> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Without<[1, 2], 1>, [2]>>, Expect<Equal<Without<[1, 2, 4, 1, 5], [1, 2]>, [4, 5]>>, Expect<Equal<Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>, []>>, ]

풀이

  • 특정 수를 제거하기 위해 배열을 유니온으로 만들고 필터링

5140 - Trunc

문제: Implement the type version of Math.trunc, which takes string or number and returns the integer part of a number by removing any fractional digits.
/* _____________ 여기에 코드 입력 _____________ */ type NoneToZero<T extends string> = T extends '-' ? '-0' : T extends '' ? '0' : T; type Trunc<T extends number | string> = `${T}` extends `${infer I}.${infer _}` ? `${NoneToZero<I>}` : `${T}`; type Test = Trunc<'0.1'> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Trunc<0.1>, '0'>>, Expect<Equal<Trunc<0.2>, '0'>>, Expect<Equal<Trunc<1.234>, '1'>>, Expect<Equal<Trunc<12.345>, '12'>>, Expect<Equal<Trunc<-5.1>, '-5'>>, Expect<Equal<Trunc<'.3'>, '0'>>, Expect<Equal<Trunc<'1.234'>, '1'>>, Expect<Equal<Trunc<'-.3'>, '-0'>>, Expect<Equal<Trunc<'-10.234'>, '-10'>>, Expect<Equal<Trunc<10>, '10'>>, ]

풀이

  • . 아래를 제거하기 위해 템플릿 문자열로 변경 후 패턴 매칭
  • 이때, .3 , -.3 과 같은 표현을 위해 필터링을 걸어주는 타입을 만들어 필터링

5153 - IndexOf

문제: Implement a type IsTuple, which takes an input type T and returns whether T is tuple type.
/* _____________ 여기에 코드 입력 _____________ */ // type IndexOf<T extends unknown[], U, C extends never[] = []> = T extends [infer First, ...infer Rest] ? // First extends U ? // U extends First ? // C['length'] : // IndexOf<Rest, U, [...C, never]> : // IndexOf<Rest, U, [...C, never]> : // -1; type IndexOf<T extends unknown[], U, C extends never[] = []> = T extends [infer First, ...infer Rest] ? Equal<First, U> extends true ? C['length'] : IndexOf<Rest, U, [...C, never]> : -1; type Test = [string] extends [any] ? true : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IndexOf<[1, 2, 3], 2>, 1>>, Expect<Equal<IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 2>>, Expect<Equal<IndexOf<[0, 0, 0], 2>, -1>>, Expect<Equal<IndexOf<[string, 1, number, 'a'], number>, 2>>, Expect<Equal<IndexOf<[string, 1, number, 'a', any], any>, 4>>, Expect<Equal<IndexOf<[string, 'a'], 'a'>, 1>>, Expect<Equal<IndexOf<[any, 1], 1>, 1>>, ]

풀이

  • 순회 후 각 요소를 타겟 타입과 비교하는 로직을 거친 후 모아둔 카운터 플래그 길이 리턴

배운점

  • extends 가지고 각 요소를 명확하게 할 수 없어서… Equal 타입을 사용했음 얘는 타입스크립트 유틸리티 타입은 아니고, 플레이그라운드에서 비교 시 사용하던 pre defined 타입
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false

13주차 타입챌린지 스터디

5310 - Join

Implement the type version of Array.join, Join<T, U> takes an Array T, string or number U and returns the Array T with U stitching up.
type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e' type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World' type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212' type Res3 = Join<["o"], "u">; // expected to be 'o'
/* _____________ 여기에 코드 입력 _____________ */ type RemoveTail<T extends string, U extends string | number> = `${T}` extends `${infer F}${U}` ? F : T; type MergeString<T extends string[], U extends string | number> = T extends [infer F extends string, ...infer R extends string[]] ? `${F}${U}${MergeString<R, U>}` : T extends string ? `${T}` : ''; type Join<T extends string[], U extends string | number = ","> = RemoveTail<MergeString<T, U>, U> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Join<['a', 'p', 'p', 'l', 'e'], '-'>, 'a-p-p-l-e'>>, Expect<Equal<Join<['Hello', 'World'], ' '>, 'Hello World'>>, Expect<Equal<Join<['2', '2', '2'], 1>, '21212'>>, Expect<Equal<Join<['o'], 'u'>, 'o'>>, Expect<Equal<Join<[], 'u'>, ''>>, Expect<Equal<Join<['1', '1', '1']>, '1,1,1'>>, ]

풀이

  1. 템플릿 문자열 형태로 합침
  1. 단, MergeString 타입처럼 합치면 뒤에 Join key 가 남으므로, 지워주는 타입 추가해서 래핑
  1. 테스트케이스에 Join key 값이 , 가 디폴트로 되어있으므로 디폴트 지정

배운점

  • 문자열 안에서도 재귀 타입이 잘 들어간다는 것 상기

5317 - LastIndexOf

Implement the type version of Array.lastIndexOf, LastIndexOf<T, U> takes an Array T, any U and returns the index of the last U in Array T
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3 type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
/* _____________ 여기에 코드 입력 _____________ */ type LastIndexOf<T extends unknown[], U> = T extends [...infer R, infer L] ? Equal<L, U> extends true ? R['length'] : LastIndexOf<R, U> : -1; type Test = LastIndexOf<[1,2,3,4], 5> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>, Expect<Equal<LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>, Expect<Equal<LastIndexOf<[0, 0, 0], 2>, -1>>, Expect<Equal<LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>, Expect<Equal<LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>, ]

풀이

  1. 뒤에서 부터 순회하며 L 이 같은지 확인, 인덱스는 length-1 이므로 Rest 의 길이를 리턴한다.

배운점

  • 뒤에서 부터 순회 가능했다는 점 상기
  • length-1Rest 배열의 length 로 가져오는 아이디어 좋았다

5360 - Unique

Implement the type version of Lodash.uniq, Unique<T> takes an Array T, returns the Array T without repeated values.
type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3] type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7] type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"] type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"] type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
/* _____________ 여기에 코드 입력 _____________ */ // type Unique<T extends unknown[], U = never, Acc extends unknown[] = []> = T extends [infer F, ...infer R] ? // F extends U ? // Unique<R, U, Acc>: // Unique<R, U | F, [...Acc, F]>: // Acc; // type Test = Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]> type Includes<T extends readonly unknown[], U> = T extends [infer F, ...infer R] ? Equal<F, U> extends true ? true : Includes<R, U> : false; type Unique<T extends unknown[], Acc extends unknown[] = []> = T extends [infer F, ...infer R] ? Includes<Acc, F> extends true ? Unique<R, Acc> : Unique<R, [...Acc, F]> : Acc; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Unique<[1, 1, 2, 2, 3, 3]>, [1, 2, 3]>>, Expect<Equal<Unique<[1, 2, 3, 4, 4, 5, 6, 7]>, [1, 2, 3, 4, 5, 6, 7]>>, Expect<Equal<Unique<[1, 'a', 2, 'b', 2, 'a']>, [1, 'a', 2, 'b']>>, Expect<Equal<Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]>, [string, number, 1, 'a', 2, 'b']>>, Expect<Equal<Unique<[unknown, unknown, any, any, never, never]>, [unknown, any, never]>>, ]

풀이

  1. 첫 시도 로직은 U 에 유니온을 쌓아가며 필터를 만들려고 했으나, 유니온으로는 anynumber 등 원시 타입이 등장 시 제대로 구분이 안 된다는 것을 파악
  1. 쌓아가고있는 정답 배열을 순회하며 하나하나 비교 후 중복 확인하는 방식으로 변경

배운점

  • 유니온이 좋은 필터 역할을 하긴 하지만, 이는 리터럴 타입일 때 유용하며 원시 타입 일 때는 알아서 잘 걸러야함
 

5821 - MapTypes

Implement MapTypes<T, R> which will transform types in object T to different types defined by type R which has the following structure
/* _____________ 여기에 코드 입력 _____________ */ type MapTypesInput = { mapFrom: unknown, mapTo: unknown }; // type MapTypes<T extends object, R extends MapTypesInput> = {[K in keyof T]: T[K] extends R['mapFrom'] ? R['mapFrom'] extends T[K] ? R['mapTo'] : never : T[K]}; type MapTypes<T extends object, R extends MapTypesInput> = {[K in keyof T]: T[K] extends R['mapFrom'] ? R extends { mapFrom: T[K] } ? R['mapTo'] : never : T[K]}; type Test = MapTypes<{ name: string, date: Date }, { mapFrom: string, mapTo: boolean } | { mapFrom: Date, mapTo: string }> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MapTypes<{ stringToArray: string }, { mapFrom: string, mapTo: [] }>, { stringToArray: [] }>>, Expect<Equal<MapTypes<{ stringToNumber: string }, { mapFrom: string, mapTo: number }>, { stringToNumber: number }>>, Expect<Equal<MapTypes<{ stringToNumber: string, skipParsingMe: boolean }, { mapFrom: string, mapTo: number }>, { stringToNumber: number, skipParsingMe: boolean }>>, Expect<Equal<MapTypes<{ date: string }, { mapFrom: string, mapTo: Date } | { mapFrom: string, mapTo: null }>, { date: null | Date }>>, Expect<Equal<MapTypes<{ date: string }, { mapFrom: string, mapTo: Date | null }>, { date: null | Date }>>, Expect<Equal<MapTypes<{ fields: Record<string, boolean> }, { mapFrom: Record<string, boolean>, mapTo: string[] }>, { fields: string[] }>>, Expect<Equal<MapTypes<{ name: string }, { mapFrom: boolean, mapTo: never }>, { name: string }>>, Expect<Equal<MapTypes<{ name: string, date: Date }, { mapFrom: string, mapTo: boolean } | { mapFrom: Date, mapTo: string }>, { name: boolean, date: string }>>, ]

풀이

  1. 첫 풀이는 마지막 케이스를 통과하지 못 함
    1. R[’mapFrom’] 이 유니온 형태(string | Date)로 들어오기 때문에 항상 never 로 빠짐
  1. 두번째 풀이는
    1. R 이 유니온 형태({ mapFrom: string, mapTo: boolean } | { mapFrom: Date, mapTo: string })로 들어온 후 { mapFrom: T[K] } 이 타입으로 걸러지므로 R 이 포함된 하나만 빠져나오게 됨

배운점

  • 제네릭은 분배 조건부 연산이 default 이며, 아래와 같은 방법으로 유니온 타입을 필터링 할 수 있다.
type UnionTest1<T> = T extends 1 ? T : never; // 1 -> 1 | never | never 로 1임 type UnionTest1Test = UnionTest<1 | 2 | 3>; type UnionTest2<T> = T extends { mapFrom: string } ? T : never; // {mapFrom: string} -> {mapFrom: string} | never 로 {mapFrom: string} 임 type UnionTest2Test = UnionTest<{ mapFrom: Date} | {mapFrom: string}>; // false type UnionTest3 = string | Date extends string ? true : false; type UnionTest4<T> = [T] extends [1] ? T : never; // never type UnionTest4Test = UnionTest4<1|2|3>

7544 - Construct Tuple

Construct a tuple with a given length.
/* _____________ 여기에 코드 입력 _____________ */ type ConstructTuple<L extends number, C extends unknown[] = []> = C['length'] extends L ? C : ConstructTuple<L, [...C, unknown]>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ConstructTuple<0>, []>>, Expect<Equal<ConstructTuple<2>, [unknown, unknown]>>, Expect<Equal<ConstructTuple<999>['length'], 999>>, // @ts-expect-error Expect<Equal<ConstructTuple<1000>['length'], 1000>>, ]

풀이

  1. 자주 사용하던 누적 배열을 이용해 길이 만큼 재귀

배운점

  • 재귀는 1000 depth 부터 에러가 나나보다.

8640 - Number Range

Sometimes we want to limit the range of numbers…
/* _____________ 여기에 코드 입력 _____________ */ type NumberRange<L extends number, H extends number, U = never, C extends never[] = [], Flag extends boolean = false> = C['length'] extends H ? U | C['length'] : Flag extends true ? NumberRange<L, H, U | C['length'], [...C, never], true>: C['length'] extends L ? NumberRange<L, H, U | C['length'], [...C, never], true>: NumberRange<L, H, U, [...C, never], false>; type Test = NumberRange<2,5> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Result1 = | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 type Result2 = | 0 | 1 | 2 type Result3 = | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 type cases = [ Expect<Equal<NumberRange<2, 9>, Result1>>, Expect<Equal<NumberRange<0, 2>, Result2>>, Expect<Equal<NumberRange<0, 140>, Result3>>, ]

풀이

  • 배열을 늘려가며 (C['length'] 를 기반으로) 누적 유니온 타입에 추가할 숫자 마련
  • H 가 높은 수 이므로 이에 도달하면 재귀 종료
  • 아닌 경우 Flag 확인 후 쭉 이어나감, Flag 는 누적 배열 길이가 L 이상인지 판별함(이전 문제에서 해결했던 방식 사용)

14주차 타입챌린지 스터디

8767 - Combination

Given an array of strings, do Permutation & Combination.
It's also useful for the prop types like video controlsList
/* _____________ 여기에 코드 입력 _____________ */ type Combination<T extends string[], U = T[number], I = U> = I extends string ? I | `${I} ${Combination<[], Exclude<U, I>>}` : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Combination<['foo', 'bar', 'baz']>, 'foo' | 'bar' | 'baz' | 'foo bar' | 'foo bar baz' | 'foo baz' | 'foo baz bar' | 'bar foo' | 'bar foo baz' | 'bar baz' | 'bar baz foo' | 'baz foo' | 'baz foo bar' | 'baz bar' | 'baz bar foo'>>, Expect<Equal<Combination<['apple', 'banana', 'cherry']>, 'apple' | 'banana' | 'cherry' | 'apple banana' | 'apple cherry' | 'banana apple' | 'banana cherry' | 'cherry apple' | 'cherry banana' | 'apple banana cherry' | 'apple cherry banana' | 'banana apple cherry' | 'banana cherry apple' | 'cherry apple banana' | 'cherry banana apple'>>, Expect<Equal<Combination<['red', 'green', 'blue', 'yellow']>, 'red' | 'green' | 'blue' | 'yellow' | 'red green' | 'red blue' | 'red yellow' | 'green red' | 'green blue' | 'green yellow' | 'blue red' | 'blue green' | 'blue yellow' | 'yellow red' | 'yellow green' | 'yellow blue' | 'red green blue' | 'red green yellow' | 'red blue green' | 'red blue yellow' | 'red yellow green' | 'red yellow blue' | 'green red blue' | 'green red yellow' | 'green blue red' | 'green blue yellow' | 'green yellow red' | 'green yellow blue' | 'blue red green' | 'blue red yellow' | 'blue green red' | 'blue green yellow' | 'blue yellow red' | 'blue yellow green' | 'yellow red green' | 'yellow red blue' | 'yellow green red' | 'yellow green blue' | 'yellow blue red' | 'yellow blue green' | 'red green blue yellow' | 'red green yellow blue' | 'red blue green yellow' | 'red blue yellow green' | 'red yellow green blue' | 'red yellow blue green' | 'green red blue yellow' | 'green red yellow blue' | 'green blue red yellow' | 'green blue yellow red' | 'green yellow red blue' | 'green yellow blue red' | 'blue red green yellow' | 'blue red yellow green' | 'blue green red yellow' | 'blue green yellow red' | 'blue yellow red green' | 'blue yellow green red' | 'yellow red green blue' | 'yellow red blue green' | 'yellow green red blue' | 'yellow green blue red' | 'yellow blue red green' | 'yellow blue green red'>> , Expect<Equal<Combination<['one', 'two']>, 'one' | 'two' | 'one two' | 'two one'>>, ]

풀이

  1. 이전 컴비네이션과 같이, 전체 튜플을 유니온 화 한 U 타입과 특정 I 1개를 선택 해 빼는 방식으로 분배
  1. I extends string 을 통해 1개 씩 I 가 분배되도록 한다.

배운점

  • U = T[number] 를 통해 튜플 내 모든 요소를 유니온으로 만들 수 있다.
type Tuple = [1,2,3]; type U = Tuple[number]; // 1 | 2 | 3
  • controlsList
html video, audio 요소에서 사용되는 속성으로, 비디오나 오디오 요소 안의 특정 기본 컨트롤 버튼을 숨길 수 있도록 만들어져있다.
const audio = document.createElement("audio"); audio.controlsList = "nodownload";
  • DOMTokenList
DOM 속성에 들어가는 Element.classListHTMLLinkElement.relList 등 문자열을 공백과 나눈 토큰이다. class 속성 등 문자열과 공백으로 나눈 속성값을 해석하기 위해 만들어진 웹 API 인터페이스
<p class="a b c"></p>
let para = document.querySelector("p"); let classes = para.classList; para.classList.add("d"); para.textContent = `paragraph classList is "${classes}"`;
tailwind 는 따로 파서가 있다고 함
 
  • 풀이 중 이해할 수 없던 현상
type TwoCombination<A extends string, B extends string> = A | B | `${A} ${B}` | `${B} ${A}`; type Combination<T extends string[]> = T extends [infer F extends string, ...infer R extends string[]] ? TwoCombination<F, Combination<R>> : never; // "a" | TwoCombination<"b", "c"> | "a b" | "a c" | "a b c" | "a c b" | "b a" | "c a" | "b c a" | "c b a" type Test = Combination<['a', 'b', 'c']>
TwoCombination“b” | “c” | “b c” | “c b” 로 해석되지 않고 타입 그대로로 해석 되었는가?
  • 타입스크립트 타입 시스템은 Lazy 하게 타입을 평가하기 때문

8987 - Subsequence

Given an array of unique elements, return all possible subsequences.
A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements.
type A = Subsequence<[1, 2]> // [] | [1] | [2] | [1, 2]
/* _____________ 여기에 코드 입력 _____________ */ type Subsequence<T extends any[]> = T extends [infer F, ...infer R extends any[]] ? [F] | [...Subsequence<R>] | [F, ...Subsequence<R>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Subsequence<[1, 2]>, [] | [1] | [2] | [1, 2]>>, Expect<Equal<Subsequence<[1, 2, 3]>, [] | [1] | [2] | [1, 2] | [3] | [1, 3] | [2, 3] | [1, 2, 3]>>, Expect<Equal<Subsequence<[1, 2, 3, 4, 5]>, [] | [1] | [2] | [3] | [4] | [5] | [1, 2] | [1, 3] | [1, 4] | [1, 5] | [2, 3] | [2, 4] | [2, 5] | [3, 4] | [3, 5] | [4, 5] | [1, 2, 3] | [1, 2, 4] | [1, 2, 5] | [1, 3, 4] | [1, 3, 5] | [1, 4, 5] | [2, 3, 4] | [2, 3, 5] | [2, 4, 5] | [3, 4, 5] | [1, 2, 3, 4] | [1, 2, 3, 5] | [1, 2, 4, 5] | [1, 3, 4, 5] | [2, 3, 4, 5] | [1, 2, 3, 4, 5] >>, Expect<Equal<Subsequence<['a', 'b', 'c']>, [] | ['a'] | ['b'] | ['c'] | ['a', 'b'] | ['a', 'c'] | ['b', 'c'] | ['a', 'b', 'c'] >>, Expect<Equal<Subsequence<['x', 'y']>, [] | ['x'] | ['y'] | ['x', 'y'] >>, ]

풀이

  1. 유니온은 중복을 알아서 제거하므로 경우의 수 다 넣음

9142 - CheckRepeatedChars

Implement type CheckRepeatedChars<S> which will return whether type S contains duplicated chars?
type CheckRepeatedChars<'abc'> // false type CheckRepeatedChars<'aba'> // true
/* _____________ 여기에 코드 입력 _____________ */ type CheckRepeatedChars<T extends string, U = never> = `${T}` extends `${infer F}${infer R}` ? F extends U ? true : CheckRepeatedChars<R, U | F> : false; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<CheckRepeatedChars<'abc'>, false>>, Expect<Equal<CheckRepeatedChars<'abb'>, true>>, Expect<Equal<CheckRepeatedChars<'cbc'>, true>>, Expect<Equal<CheckRepeatedChars<''>, false>>, ]

풀이

  1. 리터럴 타입은 유니온으로 필터링이 가능하기 때문에 하나하나 잘라가며 유니온필터 U 에 쌓고 확인하는 방식

9286 - FirstUniqueCharIndex

Given a string s, find the first non-repeating character in it and return its index. If it does not exist, return -1. (Inspired by leetcode 387)
/* _____________ 여기에 코드 입력 _____________ */ type Includes<T extends string, C extends string> = `${C}` extends `${infer _}${T}${infer _}` ? true : false; type IncludesTest = Includes<"o", 'olee'>; type FirstUniqueCharIndex<T extends string, H extends string = '', I extends never[] = []> = `${T}` extends `${infer F}${infer R}` ? Includes<F, `${H}${R}`> extends true ? FirstUniqueCharIndex<R, `${H}${F}`, [...I, never]> : I['length'] : -1; type Test = FirstUniqueCharIndex<'loveleetcode'>;

풀이

  1. Includes 타입은 C 문자열에 T 문자가 존재하는지 할 수 있다.
  1. 이를 통해 탐색을 이미 한 문자를 H 에 쌓아가며 합쳐 Includes 타입을 통해 검증
  1. I 를 통해 인덱스 리턴

9616 - Parse URL Params

You're required to implement a type-level parser to parse URL params string into an Union.
type Test1 = ParseUrlParams<':id'> // id type Test2 = ParseUrlParams<'posts/:id'> // id type Test3 = ParseUrlParams<'posts/:id/:user'> // id | user
/* _____________ 여기에 코드 입력 _____________ */ type ParseParams<Params extends string> = `${Params}` extends `:${infer Param}/${infer R}` ? Param | ParseParams<R> : `${Params}` extends `:${infer P}` ? P : never; type ParseUrlParams<T extends string> = `${T}` extends `${infer _}/${infer Params}` ? ParseParams<Params> : ParseParams<T>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ParseUrlParams<''>, never>>, Expect<Equal<ParseUrlParams<':id'>, 'id'>>, Expect<Equal<ParseUrlParams<'posts/:id'>, 'id'>>, Expect<Equal<ParseUrlParams<'posts/:id/'>, 'id'>>, Expect<Equal<ParseUrlParams<'posts/:id/:user'>, 'id' | 'user'>>, Expect<Equal<ParseUrlParams<'posts/:id/:user/like'>, 'id' | 'user'>>, ]

풀이

  1. url 부분과 path param 부분 나눈 후 path param 부분 중에서 : 가 붙어있는걸 가져오는 방식
      • 두번째 케이스인 :id 때문에 이렇게 굳이 나눴음
  1. ParseParam 타입에서 예외 케이스인 부분 따로 로직 추가

배운점

type ParseUrlParams<T> = T extends `${string}:${infer R}` ? R extends `${infer P}/${infer L}` ? P | ParseUrlParams<L> : R : never
답지를 보니 이런 깔끔한 방법도 있었음
  1. 결국 : 뒤의 어떤 것을 구하는 것 이므로, : 를 먼저 검사
  1. R/ 에 의해 더 나뉘어 질 수 없다면 리턴 아니라면 재귀

9896 - GetMiddleElement

Get the middle element of the array by implementing a GetMiddleElement method, represented by an array
  • If the length of the array is odd, return the middle element
  • If the length of the array is even, return the middle two elements
type simple1 = GetMiddleElement<[1, 2, 3, 4, 5]>, // expected to be [3] type simple2 = GetMiddleElement<[1, 2, 3, 4, 5, 6]> // expected to be [3, 4]
/* _____________ 여기에 코드 입력 _____________ */ type IsEven<T extends number, C extends never[] = [], R extends boolean = true> = C['length'] extends T ? R : IsEven<T, [...C, never], R extends true ? false : true>; type IsEvenTest = IsEven<999>; type IsEvenTest2 = IsEven<998>; type SmallMinusOne<T extends number, C extends never[] = []> = C['length'] extends T ? C extends [infer _, ...infer R] ? R['length'] : never : SmallMinusOne<T, [...C, never]>; type SmallMinusOneTest = SmallMinusOne<999>; type SmallPlusOne<T extends number, C extends never[] = []> = C['length'] extends T ? [...C, never]['length'] : SmallPlusOne<T, [...C, never]>; type SmallPlusOneTest = SmallPlusOne<999>; type GetMiddleElementForOdd<T extends unknown[], Forward extends number = 0, Backward extends number = SmallMinusOne<T['length']>> = Forward extends Backward ? [T[Forward]] : GetMiddleElementForOdd<T, SmallPlusOne<Forward>, SmallMinusOne<Backward>>; type GetMiddleElementForOddTest = GetMiddleElementForOdd<[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]>; type GetMiddleElementForEven<T extends unknown[], Forward extends number = 0, Backward extends number = SmallMinusOne<T['length']>> = T['length'] extends 0 ? [] : Forward extends SmallMinusOne<Backward> ? [T[Forward], T[Backward]] : GetMiddleElementForEven<T, SmallPlusOne<Forward>, SmallMinusOne<Backward>>; type GetMiddleElementForEvenTest = GetMiddleElementForEven<[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]>; type GetMiddleElement<T extends unknown[]> = IsEven<T['length']> extends true ? GetMiddleElementForEven<T> : GetMiddleElementForOdd<T>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<GetMiddleElement<[]>, []>>, Expect<Equal<GetMiddleElement<[1, 2, 3, 4, 5]>, [3]>>, Expect<Equal<GetMiddleElement<[1, 2, 3, 4, 5, 6]>, [3, 4]>>, Expect<Equal<GetMiddleElement<[() => string]>, [() => string]>>, Expect<Equal<GetMiddleElement<[() => number, '3', [3, 4], 5]>, ['3', [3, 4]]>>, Expect<Equal<GetMiddleElement<[() => string, () => number]>, [() => string, () => number]>>, Expect<Equal<GetMiddleElement<[never]>, [never]>>, ] // @ts-expect-error type error = GetMiddleElement<1, 2, 3>

풀이

  1. 전체 로직은 앞 뒤 인덱스를 동시에 움직여 가운데 찾는 방식, 원소의 수가 홀수라면 인덱스가 같을 때, 원소의 수가 짝수라면 인덱스가 1 차이 날 때 그 각각의 값들을 리턴
  1. 이를 위해 IsEven , GetMiddleElementForEven , GetMiddleElementForOdd 타입을 나누고, SmallPlusOne , SmallMinusOne 유틸 타입으로 인덱스 움직이며 로직 구현

배운점

type GetMiddleElement<T extends any[]> = T['length'] extends 0 | 1 | 2? T: T extends [any,...infer M,any]? GetMiddleElement<M>:never
답지를 보니, 양 끝을 동시에 하나씩 제거하는 방식으로 구현…
  • 문자열 리터럴 처럼 배열도 양 끝을 한번에 처리 할 수 있다.
type GetMiddleElement<T extends any[]> = T extends [infer F, ...infer R, infer L] ? R extends [] ? [F, L] : GetMiddleElement<R> :

15주차 타입챌린지 스터디

9898 - Appear only once

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].
/* _____________ 여기에 코드 입력 _____________ */ // 노션에서 색 구분이 이상하게 되어 주석처리, 실제 코드에선 사용 // type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; type Has<T extends any[], U> = T extends [infer F, ...infer R] ? IsEqual<F, U> extends true ? true : Has<R, U> : false; type EleminateElement<T extends any[], U, C extends any[] = []> = T extends [infer F, ...infer R] ? IsEqual<F, U> extends true ? EleminateElement<R, U, C> : EleminateElement<R, U, [...C, F]> : C; type EleminateElementTest = EleminateElement<[1,2,3], 0> type FindEles<T extends any[], U extends any[] = [], C extends any[] = []> = T extends [infer F, ...infer R] ? Has<U, F> extends true ? FindEles<R, [...U, F], EleminateElement<C, F>> : FindEles<R, [...U, F], [...C, F]> : C; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FindEles<[1, 2, 2, 3, 3, 4, 5, 6, 6, 6]>, [1, 4, 5]>>, Expect<Equal<FindEles<[2, 2, 3, 3, 6, 6, 6]>, []>>, Expect<Equal<FindEles<[1, 2, 3]>, [1, 2, 3]>>, Expect<Equal<FindEles<[1, 2, number]>, [1, 2, number]>>, Expect<Equal<FindEles<[1, 2, number, number]>, [1, 2]>>, ]

풀이

  1. 리터럴 외의 값도 필터링 할 수 있어야하므로 유니온 필터 대신 튜플로 확인
  1. 필터용 배열 하나, 리턴용 배열 하나 따로만들고, 필터는 계속 쌓아가고, 리턴은 필터에 걸리면 그 값을 찾아 제거한다.
  1. 특정 값을 제거하는 타입을 만들어 제거한다. 못 찾으면 그대로 리턴하게 만들었다.

배운점

type FindEles<T extends any[], Duplicates = never> = T extends [ infer F, ...infer R ] ? F extends Duplicates ? FindEles<R, Duplicates> : F extends R[number] ? FindEles<R, Duplicates | F> : [F, ...FindEles<R, Duplicates>] : [];
답을 보았을 때, 다음과 같이 제출한게 있었는데… 아마 이건 안 될듯? 튜플 안에 primary 타입이 들어갈 수 있기 때문에 유니온 필터링은 안 된다.
type IncludesInUnion<U, T> = [U] extends [never] ? false : U extends T ? true : false; type FindEles< T extends unknown[], Duplicates extends unknown[] = [], > = T extends [infer Head, ...infer Tail] ? IncludesInUnion<Duplicates[number] | Tail[number], Head> extends false ? [Head, ...FindEles<Tail, Duplicates>] : FindEles<Tail, [...Duplicates, Head]> : T;
  1. 다음과 같이 return 값의 복사본을 가지고 있다가
  1. 필터를 통과 할 때는 복사본과 합치고,
  1. 필터를 통과 못 할 때는 복사본을 그대로 사용한다

9989 - Count Element Number To Object

With type CountElementNumberToObject, get the number of occurrences of every item from an array and return them in an object. For example:
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 // }
/* _____________ 여기에 코드 입력 _____________ */ type SimplePlusOne<N, L extends never[] = []> = N extends number ? L['length'] extends N ? [...L, never]['length'] : SimplePlusOne<N, [...L, never]> : never; type SimplePlusOneTest = SimplePlusOne<1>; type Flattern<T extends unknown[]> = T extends [infer F, ...infer R] ? F extends unknown[] ? [...Flattern<F>, ...Flattern<R>] : [F, ...Flattern<R>] : T; type FlatternTest = Flattern<[1,2,3,[1,2,[3,4,5]]]>; type CountElement<T extends unknown[], C extends Record<PropertyKey, unknown> = {}> = [T] extends [never] ? {} : T extends [infer F extends PropertyKey, ...infer R extends PropertyKey[]] ? F extends keyof C ? CountElement<R, {[K in keyof C]: K extends F ? SimplePlusOne<C[K]> : C[K]}>: CountElement<R, C & Record<F,1>>: C; type CountElementTest = CountElement<[1,2,3,4,1]>; type IterateElement<T> = {[K in keyof T]: T[K]} type CountElementNumberToObject<T extends unknown[]> = IterateElement<CountElement<Flattern<T>>>; type Test = CountElementNumberToObject<[1,2,3,[1,2,3]]> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<CountElementNumberToObject<[1, 2, 3, 4, 5]>, { 1: 1 2: 1 3: 1 4: 1 5: 1 } >>, Expect<Equal<CountElementNumberToObject<[1, 2, 3, 4, 5, [1, 2, 3]]>, { 1: 2 2: 2 3: 2 4: 1 5: 1 }>>, Expect<Equal<CountElementNumberToObject<[1, 2, 3, 4, 5, [1, 2, 3, [4, 4, 1, 2]]]>, { 1: 3 2: 3 3: 2 4: 3 5: 1 }>>, Expect<Equal<CountElementNumberToObject<[never]>, {}>>, Expect<Equal<CountElementNumberToObject<['1', '2', '0']>, { 0: 1 1: 1 2: 1 }>>, Expect<Equal<CountElementNumberToObject<['a', 'b', ['c', ['d']]]>, { 'a': 1 'b': 1 'c': 1 'd': 1 }>>, ]

풀이

  1. 유니온은 중복을 알아서 제거하므로 경우의 수 다 넣음

배운점

// 틀린 답 type CountElement<T extends unknown[], C extends Record<PropertyKey, unknown> = {}> = [T] extends [never] ? {} : T extends [infer F extends PropertyKey, ...infer R extends PropertyKey[]] ? CountElement<R, {[K in keyof C]: K extends F ? SimplePlusOne<C[K]>: C[K]} & Record<F, 1>> : C; // 둘을 동시에 만족하는 집합은 없으므로 never type Test = {1:1} & {1:2}
풀이 과정 중 Test 와 같은 경우의 연산이 이루어지기 때문에 답 자체가 never 가 뜬 적이 있었다. 속성을 합치려면 & 를 해야하는데, 이 때 중복된 속성을 제대로 제거해주지 않았기 때문
type Flatten<T,R extends any[] = []> = T extends [infer F,...infer L]? [F] extends [never]? Flatten<L,R>: F extends any[]? Flatten<L,[...R,...Flatten<F>] > :Flatten<L,[...R,F]> :R 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 > = Count<Flatten<T>>
다음과 같이 배열을 직접 리턴값에 두고, 어차피 한번 순회 할 거 길이를 넣어주도록 한 풀이가 인상깊었다.

10969 - Integer

Please complete type Integer<T>, type T inherits from number, if T is an integer return it, otherwise return never.
/* _____________ 여기에 코드 입력 _____________ */ type Integer<T> = number extends T ? never : T extends number ? `${T}` extends `${infer _}.${infer _}` ? never : T : never; type Test = Integer<number> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' let x = 1 let y = 1 as const type cases1 = [ Expect<Equal<Integer<1>, 1>>, Expect<Equal<Integer<1.1>, never>>, Expect<Equal<Integer<1.0>, 1>>, Expect<Equal<Integer<1.000000000>, 1>>, Expect<Equal<Integer<0.5>, never>>, Expect<Equal<Integer<28.00>, 28>>, Expect<Equal<Integer<28.101>, never>>, Expect<Equal<Integer<typeof x>, never>>, Expect<Equal<Integer<typeof y>, 1>>, ]

풀이

  1. number 의 경우를 걸러내야하므로 number extends T 를 선행
  1. 템플릿 리터럴로 확인

배운점

type Integer<T extends number> = `${T}` extends `${bigint}` ? T : never
bigint 란? 타입스크립트 예약어로 BigInt 의 타입이다. 얘는 integer 만 되므로, bigint 타입을 사용해 integer 인지 확인 할 수 있다는 답지도 있었다.

16259 - ToPrimitive

Convert a property of type literal (label type) 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 } } type Todo = ToPrimitive<X> // should be same as `Expected`
/* _____________ 여기에 코드 입력 _____________ */ type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends Function ? Function : T extends object ? {[K in keyof T]: ToPrimitive<T[K]>} : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type PersonInfo = { name: 'Tom' age: 30 married: false addr: { home: '123456' phone: '13111111111' } hobbies: ['sing', 'dance'] readonlyArr: readonly ['test'] fn: () => any } type ExpectedResult = { name: string age: number married: boolean addr: { home: string phone: string } hobbies: [string, string] readonlyArr: readonly [string] fn: Function } type cases = [ Expect<Equal<ToPrimitive<PersonInfo>, ExpectedResult>>, ]

풀이

  1. type 을 한번에 가져올 수는 없었으므로, primitive 먼저 일일이 검사 후 object 면 재귀

배운점

type ToPrimitive<T> = T extends object ? ( T extends (...args: never[]) => unknown ? Function : { [Key in keyof T]: ToPrimitive<T[Key]> } ) : ( // valueOf 의 return 값을 infer 로 가져올 수 있다. 이는 primitive 값도 꺼내올 수 있다. T extends { valueOf: () => infer P } ? P : T )
valueOf 도 primitive 타입이어도 사용할 수 있음. 자바스크립트 런타임에서 wrapper 객체로 바꾸어 사용하기 때문이다.

17973 - DeepMutable

Implement a generic DeepMutable<T> which make every parameter of an object - and its sub-objects recursively - mutable.
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" } } } } type Todo = DeepMutable<X> // should be same as `Expected`
/* _____________ 여기에 코드 입력 _____________ */ type DeepMutable<T extends object> = {-readonly [K in keyof T]: T[K] extends Function ? T[K] : T[K] extends object ? DeepMutable<T[K]> : T[K] } /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Test1 { readonly title: string readonly description: string readonly completed: boolean readonly meta: { readonly author: string } } type Test2 = { 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' } readonly l: readonly [ 'hi', { readonly m: readonly ['hey'] }, ] } } } interface DeepMutableTest1 { title: string description: string completed: boolean meta: { author: string } } type DeepMutableTest2 = { a: () => 1 b: string c: { d: boolean e: { g: { h: { i: true j: 's' } k: 'hello' } l: [ 'hi', { m: ['hey'] }, ] } } } type cases = [ Expect<Equal<DeepMutable<Test1>, DeepMutableTest1>>, Expect<Equal<DeepMutable<Test2>, DeepMutableTest2>>, ] type errors = [ // @ts-expect-error DeepMutable<'string'>, // @ts-expect-error DeepMutable<0>, ]

풀이

  1. 단순히 재귀적으로 readonly 를 지우는 타입인데…

배운점

/** type Test = { a: () => 1; b: string; c: DeepMutable<{ readonly d: boolean; readonly e: { readonly g: { readonly h: { readonly i: true; readonly j: "s"; }; readonly k: "hello"; }; readonly l: readonly ["hi", { readonly m: readonly ["hey"]; }]; }; }>; } */ type Test = DeepMutable<Test2>
Test 타입은 위와 같이 해석되는데, 그래도 정답 판정이 나왔다. 이는 왜 그런걸까?
 

18142 - All

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
/* _____________ 여기에 코드 입력 _____________ */ // type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; type All<T extends unknown[], I> = T extends [infer F, ...infer R] ? IsEqual<F, I> extends false ? false : All<R, I> : true; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<All<[1, 1, 1], 1>, true>>, Expect<Equal<All<[1, 1, 2], 1>, false>>, Expect<Equal<All<['1', '1', '1'], '1'>, true>>, Expect<Equal<All<['1', '1', '1'], 1>, false>>, Expect<Equal<All<[number, number, number], number>, true>>, Expect<Equal<All<[number, number, string], number>, false>>, Expect<Equal<All<[null, null, null], null>, true>>, Expect<Equal<All<[[1], [1], [1]], [1]>, true>>, Expect<Equal<All<[{}, {}, {}], {}>, true>>, Expect<Equal<All<[never], never>, true>>, Expect<Equal<All<[any], any>, true>>, Expect<Equal<All<[unknown], unknown>, true>>, Expect<Equal<All<[any], unknown>, false>>, Expect<Equal<All<[unknown], any>, false>>, Expect<Equal<All<[1, 1, 2], 1 | 2>, false>>, ]

풀이

  1. 비즈니스로직은 어려운게 없었음 Equal 복습 느낌이었던 문제

배운점

// type IsEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false; type All<T extends unknown[], I> = IsEqual<T[number], I>; // Expect<Equal<All<[1, 1, 2], 1 | 2>, false>> -> true 로 뜸
답지 확인 결과 이런 방식도 있었으나, I 가 유니온 타입으로 들어올 경우에는 케이스가 틀리게 됨
// your answers type All<A extends unknown[], Elt> = Equal<Equal<A[number], A[0]> & Equal<A[0], Elt>, true> // Expect<Equal<All<[1|2, 2, 1|2], 1|2>, false>> -> true 로 뜸
답지 중 이런 해답도 있었다. 어느정도 일리 있긴 하지만 이는 반대로 튜플 안 원소가 유니온이고, 중간에 다른 원소가 들어가면 새로운 케이스에서는 틀리게 됨

16주차 타입챌린지 스터디

18220 - Filter

원시 타입 또는 유니온 원시 타입인 PredicatePredicate의 요소로 포함되는 배열을 반환하고, 배열 T를 가지는 Filter<T, Predicate> 타입을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type Filter<T extends any[], P> = T extends [infer F, ...infer R] ? F extends P ? [F, ...Filter<R, P>] : [...Filter<R, P>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Falsy = false | 0 | '' | null | undefined type cases = [ Expect<Equal<Filter<[0, 1, 2], 2>, [2]>>, Expect<Equal<Filter<[0, 1, 2], 0 | 1>, [0, 1]>>, Expect<Equal<Filter<[0, 1, 2], Falsy>, [0]>>, ]

풀이

  1. P 는 유니온이 가능한 원시? 타입이므로, 각각 원소들 순회 후 필터 값 일 경우 포함 아니면 미포함 하는 방식의 재귀

21104 - FindAll

Given a pattern string P and a text string T, implement the type FindAll<T, P> that returns an Array that contains all indices (0-indexed) from T where P matches.
/* _____________ 여기에 코드 입력 _____________ */ type FindAll<T extends string, P extends string, C extends number[] = [], I extends never[] = []> = P extends '' ? [] : T extends `${P}${infer _}` ? T extends `${infer _}${infer R}` ? [I['length'], ...FindAll<R, P, C, [...I, never]>] : C : T extends `${infer _}${infer R}` ? FindAll<R, P, C, [...I, never]> : C ; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<FindAll<'Collection of TypeScript type challenges', 'Type'>, [14]>>, Expect<Equal<FindAll<'Collection of TypeScript type challenges', 'pe'>, [16, 27]>>, Expect<Equal<FindAll<'Collection of TypeScript type challenges', ''>, []>>, Expect<Equal<FindAll<'', 'Type'>, []>>, Expect<Equal<FindAll<'', ''>, []>>, Expect<Equal<FindAll<'AAAA', 'A'>, [0, 1, 2, 3]>>, Expect<Equal<FindAll<'AAAA', 'AA'>, [0, 1, 2]>>, ]

풀이

  1. 일일이 배열 돌면서 풀이하기에는 너무 많은 추가적인 타입이 필요할 것 같다는 생각이 들어서 패턴 매칭을 하되, 문자 하나 씩 확인하도록 함 (마지막 케이스 때문)
  1. I 를 통해 현재 index 확인, C 를 통해 결과 누적
  1. P 가 빈 문자열일 때 처리
  1. T 를 패턴 매칭 후, 다음 문자로 넘어가도록 하고, 문자가 확인 됐다면 해당 Index 를 C 에 누적 후 C 타입 리턴

배운점

type NormalFindAll< T extends string, S extends string, P extends any[] = [], R extends number[] = [], > = T extends `${string}${infer L}`? T extends `${S}${string}`? NormalFindAll<L,S,[...P,0],[...R,P['length']]> :NormalFindAll<L,S,[...P,0],R> :R type FindAll< T extends string, P extends string, > = P extends ''? []:NormalFindAll<T,P>
답지를 보고 개선점 두가지를 보았다.
  1. infer _ 대신 string 으로 하는게 더 나았던 것 같다.
  1. 직접 리턴이 아니라 인자로 넘기는 것이라서 직접 리턴이 되는 형태로 추가하기 보단 인자에 추가하는 방식이 더 나았던 것 같다.

21106 - Combination key type

1. Combine multiple modifier keys, but the same modifier key combination cannot appear.
2. In the ModifierKeys provided, the priority of the previous value is higher than the latter value; that is, cmd ctrl is OK, but ctrl cmd is not allowed.
/* _____________ 여기에 코드 입력 _____________ */ type Combs<T extends any[]> = T extends [infer F extends string, ...infer R extends string[]] ? `${F} ${R[number]}` | Combs<R> : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type ModifierKeys = ['cmd', 'ctrl', 'opt', 'fn'] type CaseTypeOne = 'cmd ctrl' | 'cmd opt' | 'cmd fn' | 'ctrl opt' | 'ctrl fn' | 'opt fn' type cases = [ Expect<Equal<Combs<ModifierKeys>, CaseTypeOne>>, ]

풀이

  1. 1개를 선택 후 나머지 1개를 유니온 형태로 뿌려주면 되므로 R[number] 로 유니온으로 뿌려지도록 함

배운점

답지도 거의 다 같았음. 어떤 답안은 제네릭 하나 더 써서 넘기는데, 굳이 그럴 필요가 없음.
modifier keys 란?
키보드 이벤트 처리 할 때 등장하는 용어로, 사용자가 일반 키와 함께 누를 수 있는 보조 키를 말한다고 한다. 단축키를 정의 할 때 이런 용어를 사용한다.

21220 - Permutations of Tuple

Given a generic tuple type T extends unknown[], write a type which produces all permutations of T as a union.
type Test = PermutationsOfTuple<[1, number, unknown]> // Should return: // | [1, number, unknown] // | [1, unknown, number] // | [number, 1, unknown] // | [unknown, 1, number] // | [number, unknown, 1] // | [unknown, number ,1]
/* _____________ 여기에 코드 입력 _____________ */ type PermutationsOfTuple<T extends unknown[], I extends never[] = []> = T extends [] ? [] : I['length'] extends T['length'] ? never : T extends [infer F, ...infer R] ? [F, ...PermutationsOfTuple<R>] | PermutationsOfTuple<[...R, F], [...I, never]> : never /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect, ExpectFalse } from '@type-challenges/utils' type cases = [ Expect<Equal<PermutationsOfTuple<[]>, []>>, Expect<Equal<PermutationsOfTuple<[any]>, [any]>>, Expect<Equal<PermutationsOfTuple<[any, unknown]>, [any, unknown] | [unknown, any]>>, Expect<Equal< PermutationsOfTuple<[any, unknown, never]>, | [any, unknown, never] | [unknown, any, never] | [unknown, never, any] | [any, never, unknown] | [never, any, unknown] | [never, unknown, any] >>, Expect<Equal< PermutationsOfTuple<[1, number, unknown]>, | [1, number, unknown] | [1, unknown, number] | [number, 1, unknown] | [unknown, 1, number] | [number, unknown, 1] | [unknown, number, 1] >>, ExpectFalse<Equal<PermutationsOfTuple<[ 1, number, unknown ]>, [unknown]>>, ]

풀이

  1. IT 기준 재귀 할 때, 계속 같은 크기의 배열을 넣어 줄 예정이므로 브레이크 포인트를 잡기 위해 사용
  1. 순서를 바꿔가며 Permutation 재귀

배운점

T extends [infer F, ...infer R] ? [F, ...PermutationsOfTuple<R>] | PermutationsOfTuple<[...R, F], [...I, never]> : never
혼자 풀이 하면서 가장 고민이었던 부분이 하나 선택 후 나머지 값을 보존하는 방법을 고민을 했었는데,
[…R,F] 와 같이 순서를 변경할 수도 있었음.

25170 - Replace First

Implement the type ReplaceFirst<T, S, R> which will replace the first occurrence of S in a tuple T with R. If no such S exists in T, the result should be T.
/* _____________ 여기에 코드 입력 _____________ */ type ReplaceFirst<T extends readonly unknown[], S, R, U extends unknown[] = []> = T extends [infer F, ...infer L] ? F extends S ? [...U, R, ...L] : ReplaceFirst<L, S, R, [...U, F]> : U; type Test = ReplaceFirst<[1,2,3],3,4> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ReplaceFirst<[1, 2, 3], 3, 4>, [1, 2, 4]>>, Expect<Equal<ReplaceFirst<['A', 'B', 'C'], 'C', 'D'>, ['A', 'B', 'D']>>, Expect<Equal<ReplaceFirst<[true, true, true], true, false>, [false, true, true]>>, Expect<Equal<ReplaceFirst<[string, boolean, number], boolean, string>, [string, string, number]>>, Expect<Equal<ReplaceFirst<[1, 'two', 3], string, 2>, [1, 2, 3]>>, Expect<Equal<ReplaceFirst<['six', 'eight', 'ten'], 'eleven', 'twelve'>, ['six', 'eight', 'ten']>>, ]

풀이

  1. 확인한 원소를 저장하고(U), 확인 되었으면 앞에 붙이는 방식으로 구현

배운점

type ReplaceFirst<T extends readonly unknown[], S, R> = T extends readonly [infer F, ...infer Rest] ? F extends S ? [R, ...Rest] : [F, ...ReplaceFirst<Rest, S, R>] : [];
제네릭으로 저장하면서 관리하지 않고, 위의 답지와 같이 쌓아갈 수도 있었음

25270 - Transpose

The transpose of a matrix is an operator which flips a matrix over its diagonal; that is, it switches the row and column indices of the matrix A by producing another matrix, often denoted by A<sup>T</sup>.
type Matrix = Transpose <[[1]]>; // expected to be [[1]] type Matrix1 = Transpose <[[1, 2], [3, 4]]>; // expected to be [[1, 3], [2, 4]] type Matrix2 = Transpose <[[1, 2, 3], [4, 5, 6]]>; // expected to be [[1, 4], [2, 5], [3, 6]]
/* _____________ 여기에 코드 입력 _____________ */ type Transpose<M extends number[][], I extends never[] = [], J extends never[] = [], T extends number[][] = [], C extends number[] = [], IE = M['length'], JE = M[0]['length']> = J['length'] extends JE ? I['length'] extends IE ? // 종료 T: // J 는 종료, I 는 아직 미종료 Transpose<M, [...I, never], J, T, [...C, M[I['length']][J['length']]]>: I['length'] extends IE ? // J 미종료, I 종료 Transpose<M, [], [...J, never], [...T, C], []>: // 둘다 미종료 Transpose<M, [...I, never], J, T, [...C, M[I['length']][J['length']]]>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Transpose<[]>, []>>, Expect<Equal<Transpose<[[1]]>, [[1]]>>, Expect<Equal<Transpose<[[1, 2]]>, [[1], [2]]>>, Expect<Equal<Transpose<[[1, 2], [3, 4]]>, [[1, 3], [2, 4]]>>, Expect<Equal<Transpose<[[1, 2, 3], [4, 5, 6]]>, [[1, 4], [2, 5], [3, 6]]>>, Expect<Equal<Transpose<[[1, 4], [2, 5], [3, 6]]>, [[1, 2, 3], [4, 5, 6]]>>, Expect<Equal<Transpose<[[1, 2, 3], [4, 5, 6], [7, 8, 9]]>, [[1, 4, 7], [2, 5, 8], [3, 6, 9]]>>, ]

풀이

  1. I 는 원본 행렬 기준 행, J 는 열의 인덱스
  1. C 는 1차원 배열, I 를 순회하면서 쌓을 배열
  1. T 는 2차원 배열, 리턴할 전치행렬, C 를 쌓을 배열
  1. 기존 I , J 와 다르게 I 는 열, J 는 행 축으로 순회 돌도록 한다.

배운점

type Transpose<M extends number[][],R = M['length'] extends 0?[]:M[0]> = { [X in keyof R]:{ [Y in keyof M]:X extends keyof M[Y]?M[Y][X]:never } }
모든 배열 순회 돌며 뒤집기가 가능했음, 여기서 R 은 x 좌표 순회(행 방향 순회) 를 위해 사용하고, 각 x 좌표마다 y 좌표 순회를 M 에서 꺼내 뒤집은 값을 넣는다.

17주차 타입챌린지 스터디

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 와 나머지 배열에서 찾아 확인하는 방법 사용, 어차피 한번 지나친 것은 다시 볼 필요 없어서 이렇게 함

18주차 타입챌린지 스터디

28333 - Public Type

Remove the key starting with _ from given type T.
/* _____________ 여기에 코드 입력 _____________ */ type PublicType<T extends object> = {[K in keyof T as K extends `_${string}` ? never : K]: T[K]} /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<PublicType<{ a: number }>, { a: number }>>, Expect<Equal<PublicType<{ _b: string | bigint }>, {}>>, Expect<Equal<PublicType<{ readonly c?: number }>, { readonly c?: number }>>, Expect<Equal<PublicType<{ d: string, _e: string }>, { d: string }>>, Expect<Equal<PublicType<{ _f: () => bigint[] }>, {}>>, Expect<Equal<PublicType<{ g: '_g' }>, { g: '_g' }>>, Expect<Equal<PublicType<{ __h: number, i: unknown }>, { i: unknown }>>, ]

풀이

  1. key 를 순회하며 _ 가 앞에 붙은 경우 key 를 제거

배운점

  1. key 에 _ 를 넣으면 private 타입으로 하는 관용적 표현이 있다는 것 상기, private 타입은 제거하는 문제였음
  1. key 안에서도 조건부 연산으로 템플릿 리터럴 형태의 필터링이 가능하다.

29650 - ExtractToObject

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.
type Test = { id: '1', myProp: { foo: '2' }} type Result = ExtractToObject<Test, 'myProp'> // expected to be { id: '1', foo: '2' }
/* _____________ 여기에 코드 입력 _____________ */ type ExtractToObject<T, U extends keyof T> = Omit<{[K in keyof T as K extends U ? never : K]: K extends U ? never: T[K]; } & T[U], never>; type Test = ExtractToObject<test1, 'myProp'> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type test1 = { id: '1', myProp: { foo: '2' } } type testExpect1 = { id: '1' foo: '2' } type test2 = { id: '1' prop1: { zoo: '2' } prop2: { foo: '4' } } type testExpect2 = { id: '1' prop1: { zoo: '2' } foo: '4' } type test3 = { prop1: { zoo: '2', a: 2, b: 4, c: 7 } prop2: { foo: '4', v: 2, d: 4, g: 7 } k: 289 } type testExpect3 = { zoo: '2' a: 2 b: 4 c: 7 prop2: { foo: '4', v: 2, d: 4, g: 7 } k: 289 } type test4 = { id: '1', myProp: { foo: '2' } } type testExpect4 = { id: '1' myProp: { foo: '2' } } type cases = [ Expect<Equal<ExtractToObject<test1, 'myProp'>, testExpect1>>, Expect<Equal<ExtractToObject<test2, 'prop2'>, testExpect2>>, Expect<Equal<ExtractToObject<test3, 'prop1'>, testExpect3>>, // @ts-expect-error Expect<Equal<ExtractToObject<test4, 'prop4'>, testExpect4>>, ]

풀이

  1. key 순회하며, U 인 것은 제거
  1. 그리고 T[U] 와 (U extends keyof T 덕분에 가능) & 연산 후 Omit 후처리

배운점

풀이 중 이런 것도 가능했음
// 이러면 U 의 하위 key 들이 K에 함께 포함 됨 type ExtractToObject<T, U extends keyof T> = {[K in keyof T as K extends U ? keyof T[K] : K]: ...}

29785 - Deep Omit

Implement a typeDeepOmit, Like Utility types Omit, A type takes two arguments.
For example:
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: {} } }
/* _____________ 여기에 코드 입력 _____________ */ type MyOmit<T, U> = {[K in keyof T as K extends U ? never : K]: T[K]} type DeepOmit<T, U extends string> = `${U}` extends `${infer F extends string & keyof T}.${infer R}` ? {[K in keyof T]: K extends F ? DeepOmit<T[K], R> : T[K]} : MyOmit<T, U>; type Test = DeepOmit<obj, 'person'> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type obj = { person: { name: string age: { value: number } } } type cases = [ Expect<Equal<DeepOmit<obj, 'person'>, {}>>, Expect<Equal<DeepOmit<obj, 'person.name'>, { person: { age: { value: number } } }>>, Expect<Equal<DeepOmit<obj, 'name'>, obj>>, Expect<Equal<DeepOmit<obj, 'person.age.value'>, { person: { name: string, age: {} } }>>, ]

풀이

  1. U. 기준으로 나누어 재귀
  1. key 를 순회하며 DeepOmit 으로 해당 키 재귀
  1. 마지막 U 의 경우 일반적인 1 depth omit 리턴

30301 - IsOdd

return true is a number is odd
/* _____________ 여기에 코드 입력 _____________ */ type IsOddForSmall<T extends number, C extends never[] = [], OddFlag extends boolean = false> = C['length'] extends T ? OddFlag : IsOddForSmall<T, [...C, never], OddFlag extends true ? false : true>; type ReverseSplitString<T extends string, C extends string[] = []> = T extends `${infer First}${infer Rest}` ? ReverseSplitString<Rest, [First, ...C]> : C; type GetModDecimal<T extends number> = ReverseSplitString<`${T}`>[0] extends `${infer N extends number}` ? N : T; type IsOdd<T extends number> = // 소수점 처리 `${T}` extends `${string}.${string}` ? false : // 지수 표현 처리 `${T}` extends `${string}e${infer L extends number}` ? L extends 0 ? true : false : // 정수 처리 IsOddForSmall<GetModDecimal<T>>; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsOdd<5>, true>>, Expect<Equal<IsOdd<2023>, true>>, Expect<Equal<IsOdd<1453>, true>>, Expect<Equal<IsOdd<1926>, false>>, Expect<Equal<IsOdd<2.3>, false>>, Expect<Equal<IsOdd<3e23>, false>>, Expect<Equal<IsOdd<3e0>, true>>, Expect<Equal<IsOdd<number>, false>>, ]

풀이

  1. 홀수 여부는 마지막 자릿수만 판별하면 되므로, 예외 케이스 이외의 정수는 마지막 자릿수만 number 형태로 얻도록 하는 타입을 통해 마지막 자자릿수만 얻고자 함
  1. 각 수를 문자열 변환해 잘라내고 뒤집은 후, [0] 을 통해 마지막 자릿수를 얻어냄
  1. 마지막 자리수는 Flag 를 통해 배열을 늘리며 재귀가 끝났을 때 플래그 리턴하는 방식으로 홀수인지 판별
  1. 소수점을 가진 수는 홀수가 아니므로 false
  1. 지수 표현은 0 제곱인 경우 홀수, 아닌 경우 나누어 판별

배운점

테스트 케이스에 없는 케이스(3.1e1 과 같은)를 포함 할 수 있는 좀 더 정확한 정답은 소수점과 지수 표현을 섞어서 길이 표현을 해야한다고 생각해서 추가적인 테스트 케이스를 넣어 봤는데, 같은 코드로도 아래와 같은 케이스가 통과 되는 것으로 보아 지수 표현을 하더라도 내부적으로 숫자 변환을 하는 것 같음
// 제대로 true 로 통과 됨, 원 코드에서는 false 로 빠질 수도 있다고 생각했음 type case = Expect<Equal<IsOdd<3.211e3>, true>>
답안지를 보니 홀수 판별법은 하드코딩하는 것이 더 바람직한 방법인 것 같음
type IsOddForSmall<T extends number> = T extends 1 | 3 | 5 | 7 | 9 ? true : false;

30430 - Tower of hanoi

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.
/* _____________ 여기에 코드 입력 _____________ */ type Hanoi<N extends number, From = 'A', To = 'B', Intermediate = 'C', I extends never[] = []> = N extends 0 ? [] : I['length'] extends N ? [] : [ ...Hanoi<N, From, Intermediate, To, [...I, never]>, [From, To], ...Hanoi<N, Intermediate, To, From, [...I, never]> ] /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Tests = [ Expect<Equal<Hanoi<0>, []>>, Expect<Equal<Hanoi<1>, [['A', 'B']]>>, Expect<Equal<Hanoi<2>, [['A', 'C'], ['A', 'B'], ['C', 'B']]>>, Expect<Equal<Hanoi<3>, [['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B'], ['C', 'A'], ['C', 'B'], ['A', 'B']]>>, Expect<Equal<Hanoi<5>, [['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B'], ['C', 'A'], ['C', 'B'], ['A', 'B'], ['A', 'C'], ['B', 'C'], ['B', 'A'], ['C', 'A'], ['B', 'C'], ['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B'], ['C', 'A'], ['C', 'B'], ['A', 'B'], ['C', 'A'], ['B', 'C'], ['B', 'A'], ['C', 'A'], ['C', 'B'], ['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B'], ['C', 'A'], ['C', 'B'], ['A', 'B']]>>, ]

풀이

  1. Hanoi<1> 은 A → B로 옮기면 된다. From , To 를 직접 옮길 수 있다.
  1. Hanoi<2> 는 가장 작은 것을 우선 A → C 로 옮기고, 이후 1개 인 것 처럼 A → B 로 옮기고, C 로 옮겨두었던 것을 C → A 로 옮기는 과정이 있다.
  1. Hanoi<3> 은 우선 두 원판을 C 로 옮긴 후 (Hanoi<2, A, C, B>), 1개 인 것 처럼 A → B 로 옮기고, C 로 옮겨 두었던 두 원판을 B 로 옮기는 과정이 있다. (Hanoi<2, C, B, A>)
  1. 이를 통해, Hanoi<N>Hanoi<N-1> 의 타겟이 다른 방식의 재귀로 풀 수 있다는 것을 알 수 있다.

30958 - Pascal's triangle

Given a number N, construct the Pascal's triangle with N rows.
/* _____________ 여기에 코드 입력 _____________ */ type SmallAdder<A extends number, B extends number, AC extends never[] = [], BC extends never[] = []> = AC['length'] extends A ? BC['length'] extends B ? [...AC, ...BC]['length'] : SmallAdder<A,B,AC,[...BC, never]>: SmallAdder<A,B,[...AC, never],BC>; type NoneToZero<N> = N extends number ? N : 0; type Pascal<N extends number, I extends never[] = [], J extends never[] = [], C extends number[] = [1], A extends number[][] = [[1]]> = [...I, never]['length'] extends N ? A: J['length'] extends [...I, never]['length'] ? Pascal<N, [...I, never], [], [1], [...A, C]>: Pascal<N, I, [...J, never], [...C, SmallAdder<A[I['length']][J['length']], NoneToZero<A[I['length']][[...J, never]['length']]>>], A>; type Test = Pascal<1> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect< Equal< Pascal<1>, [ [1], ] > >, Expect< Equal< Pascal<3>, [ [1], [1, 1], [1, 2, 1], ] > >, Expect< Equal< Pascal<5>, [ [1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], ] > >, Expect< Equal< Pascal<7>, [ [1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1], [1, 6, 15, 20, 15, 6, 1], ] > >, ]

풀이

  1. 기본적인 아이디어는 일반적인 풀이인 이중 포문 형식으로 해결하려고 함 + 테스트 케이스의 수가 적으므로, 작은 숫자 덧셈기로 직접 더하려고 함
  1. I 는 행, J 는 열을 채울 인덱스, C 는 파스칼 삼각형의 한 depth 를 채울 배열, AC 를 누적해 쌓을 정답 2차원 배열
  1. 를 인덱스라고 하면, 에 따라서 번째 행의 번째 열에 있는 수와 번째 열에 있는 수를 더해야한다. 하지만, 어떤 수에서 -1 을 해주는 타입을 구현하여 풀이 과정에 사용하면 형식 인스턴스 깊이 에러가 나서 다음과 같은 방식으로 우회 했음.
  1. 실제 인덱스는 I , J 배열에 원소 하나를 추가한 것을 사용하고, 번째 행과 번째 열에 접근 하기 위해 -1 인 경우를 I , J 원본 배열을 사용하여 인덱스에 접근
  1. 이렇게 하면 초기 값을 반복문에서 제공할 수 없으므로, 초기 값 CA 에 1을 넣어둠
  1. 이후 이중 포문을 돌며 계산함. 이때, 접근 시 number 가 아닌 경우 0으로 매핑해 1이 계산 되도록 함

배운점

// 최대 대응 가능 수는 13까지 // 형식 인스턴스화는 깊이가 매우 깊으며 무한할 수도 있습니다. type Test = Pascal<14>
// 배열에 없는 인덱스에 접근 시 undefined 이므로, 다음과 같이 의도를 수정 할 수 있다. type NoneToZero<N> = N extends undefined ? 0 : N;

19주차 타입챌린지 스터디

30970 - IsFixedStringLiteralType

Sometimes you may want to determine whether a string literal is a definite type. For example, when you want to check whether the type specified as a class identifier is a fixed string literal type.
type Action<ID extends string> = { readonly id: ID };
Since it must be fixed, the following types must be determined as false.
/* _____________ 여기에 코드 입력 _____________ */ type IsFixedStringLiteralType<S extends string> = {} extends Record<S, 1> ? false : Equal<[S], S extends unknown ? [S] : never> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type testcase = | Expect<Equal<IsFixedStringLiteralType<'ABC'>, true>> | Expect<Equal<IsFixedStringLiteralType<string>, false>> | Expect<Equal<IsFixedStringLiteralType<'ABC' | 'DEF'>, false>> | Expect<Equal<IsFixedStringLiteralType<never>, false>> | Expect<Equal<IsFixedStringLiteralType<`${string}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${string & {}}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${number}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${bigint}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${boolean}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${true}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${false}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${null}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${undefined}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${string}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${string & {}}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${number}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${bigint}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${boolean}`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${true}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${false}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${null}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${undefined}`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${string}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${string & {}}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${number}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${bigint}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${boolean}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`${true}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${false}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${null}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`${undefined}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${string}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${string & {}}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${number}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${bigint}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${boolean}DEF`>, false>> | Expect<Equal<IsFixedStringLiteralType<`ABC${true}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${false}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${null}DEF`>, true>> | Expect<Equal<IsFixedStringLiteralType<`ABC${undefined}DEF`>, true>> | true

풀이

  1. {} extends Record<S, 1> 을 통해 고정되지 않은 대부분의 케이스를 거를 수 있다.
  1. 이후, Equal<[S], S extends unknown ? [S] : never> 를 통해 유니온이 포함된 케이스를 거른다.

배운점

  1. Record<S, 1> 에서 S 가 primary type 등 고정된 string 이 아니라면, 인덱스 시그니처 중 하나로 추가가 될 뿐이라서, {} extends Record<S, 1> 이 true 가 된다.
  1. `ABC${boolean}` 와 같이 boolean 의 경우 `ABCtrue` | `ABCfalse` 로 해석됨

34007 - Compare Array Length

Implement CompareArrayLength to compare two array length(T & U).
If length of T array is greater than U, return 1; If length of U array is greater than T, return -1; If length of T array is equal to U, return 0.
/* _____________ 여기에 코드 입력 _____________ */ type CompareArrayLength<T extends any[], U extends any[]> = // edge case: 0 T['length'] extends U['length'] ? 0: T extends [infer _, ...infer TR] ? U extends [infer _, ...infer UR] ? CompareArrayLength<TR, UR> : 1 : -1; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<CompareArrayLength<[1, 2, 3, 4], [5, 6]>, 1>>, Expect<Equal<CompareArrayLength<[1, 2], [3, 4, 5, 6]>, -1>>, Expect<Equal<CompareArrayLength<[], []>, 0>>, Expect<Equal<CompareArrayLength<[1, 2, 3], [4, 5, 6]>, 0>>, ]

풀이

  1. 두 배열을 각각 잘라가며 먼저 전부 잘리는 경우 특정 케이스 리턴
  1. 두 배열의 길이가 같은 경우 0 리턴

34857 - Defined Partial Record

Using a Record with union types as keys doesn't allow you to make an object with only some of them
const record: Record<'a' | 'b' | 'c', number> = { a: 42, b: 10 } // error: Property 'c' is missing in type '{ a: number; b: number; }' // but required in type 'Record<"a" | "b" | "c", number>'
Using a Partial Record with union types as keys allows you to make an object without all union members, but makes all keys and values optional, potentially leaving them undefined
const partial: Partial<Record<'a' | 'b' | 'c', number>> = { a: 42 } const partialType = typeof partial // { a?: number | undefined, b? : number | undefined, c? : number | undefined } const operation = 0 + partial.a // error: 'partial.a' is possibly 'undefined' const access = partial.c // possible, type doesn't know that there is no such key
You need to make a type that takes the best of both worlds, creates all combinations of all the types in the union, so using a key that exists in the object gives you a defined type, but using a key that exists in the union and not in the object throws an error
const best: DefinedPartial<Record<'a' | 'b' | 'c', number>> = { a: 42 } const sum = 0 + best.a // 42 const error = best.b // error: property 'b' does not exist on type '{ a: number; }'
/* _____________ 여기에 코드 입력 _____________ */ type DefinedPartial<T, Keys extends keyof T = keyof T> = Keys extends any ? keyof T extends any ? T | DefinedPartial<Record<Exclude<keyof T, Keys>, T[Keys]>> : never : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect, ExpectTrue, NotAny, NotEqual } from '@type-challenges/utils' type A1 = Record<'a' | 'b', string> type E1 = { a: string } | { b: string } | { a: string, b: string } type D1 = DefinedPartial<A1> type C1 = Expect<Equal<D1, E1>> type A2 = Record<'a' | 'b' | 'c', string> type E2 = { a: string } | { b: string } | { c: string } | { a: string, b: string } | { a: string, c: string } | { b: string, c: string } | { a: string, b: string, c: string } type D2 = DefinedPartial<A2> type C2 = Expect<Equal<D2, E2>> type A3 = Record<'a', number> type E3 = { a: number } type D3 = DefinedPartial<A3> type C3 = Expect<Equal<D3, E3>> type A4 = Record<'a', number> type E4 = { a: string } type D4 = DefinedPartial<A4> type C4 = ExpectTrue<NotAny<D4> | NotEqual<D4, E4>> type A5 = Record<'a' | 'c', number> type E5 = { a: string, b: string } type D5 = DefinedPartial<A5> type C5 = ExpectTrue<NotAny<D5> | NotEqual<D5, E5>> type A6 = { a: string, b: string } type E6 = { a: string } | { b: string } | { a: string, b: string } type D6 = DefinedPartial<A6> type C6 = Expect<Equal<D6, E6>>

풀이

  1. 조합 형태로 풀어내야 하므로, Keys extends anykeyof T extends any 를 통해 유니온 조건부 연산과 Keys 를 통해 해당 타입을 끌어내는 방식으로 풀이 (답지를 참고해 다시 풀었음)

배운점

/* _____________ 여기에 코드 입력 _____________ */ type DefinedRecord<T> = T extends Record<infer K extends keyof any, infer P> ? {[TK in K]: P} : never; type DefinedPartial<T, Keys extends keyof T = keyof T> = T extends Record<infer K extends keyof T, infer P> ? K extends any ? DefinedPartial<Record<Exclude<Keys, K>, P>> | DefinedRecord<T> : never: never; /** type Test = { b: unknown; } | { a: string; b: string; } | { a: unknown; } */ type Test = DefinedPartial<A6>
아까운 답이라서 남겨두었음
다음과 같은 경우 제일 첫번째 depth 를 제외한 나머지 depth 에서 P 를 반영하지 못 했음
Record<infer K extends keyof T, infer P> 에서 다음 P 로 넘길 때, 일반적으로 Punknown 으로 추론되기 때문이다.
infer 로 제네릭을 꺼낼 수도 있다.

35045 - Longest Common Prefix

Write a type, LongestCommonPrefix that returns the longest common prefix string amongst a tuple of strings.
If there is no common prefix, return an empty string "".
type Common = LongestCommonPrefix<["flower", "flow", "flight"]> // ?^ "fl" type Uncommon = LongestCommonPrefix<["dog", "racecar", "race"]> // ?^ ""
/* _____________ 여기에 코드 입력 _____________ */ type LongestCommonPrefix<T extends string[], Std extends string = T[0], P extends string = '', Answer extends string = ''> = Std extends `${infer First}${infer Rest}` ? // 기준 문자가 '' 이 아닐 때, T[number] extends `${P}${string}` ? LongestCommonPrefix<T, Rest, `${P}${First}`, P> : Answer : // 기준 문자가 '' 일 때, T[number] extends `${P}${string}` ? P : ''; type Test = LongestCommonPrefix<['a', '', 'a']> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<LongestCommonPrefix<['flower', 'flow', 'flight']>, 'fl'>>, Expect<Equal<LongestCommonPrefix<['dog', 'racecar', 'race']>, ''>>, Expect<Equal<LongestCommonPrefix<['', '', '']>, ''>>, Expect<Equal<LongestCommonPrefix<['a', '', '']>, ''>>, Expect<Equal<LongestCommonPrefix<['', 'a', '']>, ''>>, Expect<Equal<LongestCommonPrefix<['', '', 'a']>, ''>>, Expect<Equal<LongestCommonPrefix<['a', 'a', '']>, ''>>, Expect<Equal<LongestCommonPrefix<['a', '', 'a']>, ''>>, Expect<Equal<LongestCommonPrefix<['', 'a', 'a']>, ''>>, Expect<Equal<LongestCommonPrefix<['a', 'a', 'a']>, 'a'>>, Expect<Equal<LongestCommonPrefix<['abc', 'abcd', 'abcde']>, 'abc'>>, Expect<Equal<LongestCommonPrefix<[' ', ' ', ' ']>, ' '>>, Expect<Equal<LongestCommonPrefix<['type-challenges', 'type-hero', 'typescript']>, 'type'>>, ]

풀이

  1. 가장 첫번째 문자를 기준으로 앞으로 하나씩 쌓아가며 포함하는지 확인한다.
  1. Std 는 자르기 위한 기준 문자열, P 는 잘린 앞 문자열, Answer 는 정답이 될 문자열
  1. Std‘’ 가 아니라면 다음과 같이 실행한다.
    1. 모든 T 안 문자열이 P 를 포함한다면 → 재귀
    2. 포함하지 않는다면 Answer (P 에서 하나 제거된 상태) 리턴
  1. Std‘’ 라면 다음과 같이 실행한다.
    1. 모든 T 안 문자열이 P 를 포함한다면 → P 리턴
    2. 포함하지 않는다면 ‘’ 리턴 (완전 찾지 못한 경우)

배운점

type TupleExam<T extends string[], P extends string> = T[number] extends `${P}${string}` ? true : false; // true type Debug1 = TupleExam<['a', 'a', 'a'], 'a'>; // false type Debug2 = TupleExam<['a', 'b', 'c'], 'a'>;
Tuple 안의 문자들을 T[number] 를 통해 한번에 검사할 수 있다.

35191 - Trace

The trace of a square matrix is the sum of the elements on its main diagonal.
However, it's difficult to calculate the sum with type system.
To make things simple, let's return the elements on the main diagonal with union type.
For example:
type Arr = [ [1,2], [3,4] ] type Test = Trace<Arr> // expected to be 1 | 4
/* _____________ 여기에 코드 입력 _____________ */ type ValueOf<X> = X extends {[Key in keyof X]: infer Value} ? Value : never; type Trace<T extends any[][]> = {[Y in keyof T]: ValueOf<{[X in keyof T[Y] as X extends Y ? X : never]: T[Y][X]}>}[number] type Test = Trace<[[1,2], [3,4]]> /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Trace<[[1, 2], [3, 4]]>, 1 | 4>>, Expect<Equal<Trace<[[0, 1, 1], [2, 0, 2], [3, 3, 0]]>, 0>>, Expect<Equal<Trace<[['a', 'b', ''], ['c', '', ''], ['d', 'e', 'f']]>, 'a' | '' | 'f'>>, ]

풀이

  1. 이중 배열을 순회 하며, T[Y] 를 순회 할 때는 XY 가 같은 경우만을 남긴다.
  1. ValueOf<X> 타입은 객체의 value 의 타입을 유니온 형태로 리턴한다.
  1. 그러므로, 외부 순회는 {0: T[0][0]} | {1: T[1][1]} 형태로 리턴 된다.
  1. 이를 number 로 접근해 유니온 형태로 정리한다.

35252 - IsAlphabet

Determine if the given letter is an alphabet.
/* _____________ 여기에 코드 입력 _____________ */ type IsAlphabet<S extends string> = S extends Uppercase<S> ? S extends Lowercase<S> ? false : true : true; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsAlphabet<'A'>, true>>, Expect<Equal<IsAlphabet<'z'>, true>>, Expect<Equal<IsAlphabet<'9'>, false>>, Expect<Equal<IsAlphabet<'!'>, false>>, Expect<Equal<IsAlphabet<'😂'>, false>>, Expect<Equal<IsAlphabet<''>, false>>, ]

풀이

  1. 알파벳이라면, 대문자화, 소문자화 했을때 자기 자신과 비교하면 두 조건 중 하나는 false 가 나올 수 밖에 없다.
  1. 둘 다 같은 경우 false 나머지는 true