개요
타입챌린지 스터디 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>>
type AppendToObject<T, U, V> = {[index in keyof (T & U)]: index extends keyof T ? T[index] : V}
U
는 이미 keyof any
의 extends
이므로, keyof T
와 U
의 합집합을 순회함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 임{}
는null
과undefined
값을 제외한 나머지 모든 타입과 조건부 타입 연산 시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>>, ]
배운점
이전에도 한적 있지만,
T
가 never
로 들어오면 분배 조건부 타입 연산시 never
로 들어가 아예 조건부 타입 연산 자체가 누락되어버린다. (분배 조건부 타입의 연산은 우선 유니언 타입의 각 요소들을 기준으로 조건부 타입 연산을 실행하고, 후에 유니언으로 합치는 방식인데, never 가 제네릭으로 들어올 시 유니언으로 합칠때 never 이므로 누락되는 것 처럼 보임) 이를 피하기 위해서는 타입을 재가공하여 비교한다.1097 Is union
문제:
T
를 입력으로 받고, T
가 Union
유형으로 확인되는지 여부를 반환하는 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 ! '>>, ]
풀이
- 문자열 순차적으로 순회하며 C와 조건부 타입 연산
- 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
문제: 두 개의 타입 인수
T
와 K
를 사용하는 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>;
그러면 내가 적은 답은 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
순회 돌 때as
로T[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'>>, ]
풀이
- 템플릿 문자열 형태로 합침
- 단, MergeString 타입처럼 합치면 뒤에 Join key 가 남으므로, 지워주는 타입 추가해서 래핑
- 테스트케이스에 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>>, ]
풀이
- 뒤에서 부터 순회하며
L
이 같은지 확인, 인덱스는length-1
이므로 Rest 의 길이를 리턴한다.
배운점
- 뒤에서 부터 순회 가능했다는 점 상기
length-1
은Rest
배열의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]>>, ]
풀이
- 첫 시도 로직은 U 에 유니온을 쌓아가며 필터를 만들려고 했으나, 유니온으로는
any
나number
등 원시 타입이 등장 시 제대로 구분이 안 된다는 것을 파악
- 쌓아가고있는 정답 배열을 순회하며 하나하나 비교 후 중복 확인하는 방식으로 변경
배운점
- 유니온이 좋은 필터 역할을 하긴 하지만, 이는 리터럴 타입일 때 유용하며 원시 타입 일 때는 알아서 잘 걸러야함
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 }>>, ]
풀이
- 첫 풀이는 마지막 케이스를 통과하지 못 함
R[’mapFrom’]
이 유니온 형태(string | Date
)로 들어오기 때문에 항상 never
로 빠짐- 두번째 풀이는
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>>, ]
풀이
- 자주 사용하던 누적 배열을 이용해 길이 만큼 재귀
배운점
- 재귀는 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'>>, ]
풀이
- 이전 컴비네이션과 같이, 전체 튜플을 유니온 화 한
U
타입과 특정I
1개를 선택 해 빼는 방식으로 분배
I
extendsstring
을 통해 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.classList
나 HTMLLinkElement.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'] >>, ]
풀이
- 유니온은 중복을 알아서 제거하므로 경우의 수 다 넣음
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>>, ]
풀이
- 리터럴 타입은 유니온으로 필터링이 가능하기 때문에 하나하나 잘라가며 유니온필터
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'>;
풀이
Includes
타입은C
문자열에T
문자가 존재하는지 할 수 있다.
- 이를 통해 탐색을 이미 한 문자를
H
에 쌓아가며 합쳐Includes
타입을 통해 검증
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'>>, ]
풀이
- url 부분과 path param 부분 나눈 후 path param 부분 중에서
:
가 붙어있는걸 가져오는 방식 - 두번째 케이스인
:id
때문에 이렇게 굳이 나눴음
ParseParam
타입에서 예외 케이스인 부분 따로 로직 추가
배운점
type ParseUrlParams<T> = T extends `${string}:${infer R}` ? R extends `${infer P}/${infer L}` ? P | ParseUrlParams<L> : R : never
답지를 보니 이런 깔끔한 방법도 있었음
- 결국
:
뒤의 어떤 것을 구하는 것 이므로,:
를 먼저 검사
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 차이 날 때 그 각각의 값들을 리턴
- 이를 위해
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]>>, ]
풀이
- 리터럴 외의 값도 필터링 할 수 있어야하므로 유니온 필터 대신 튜플로 확인
- 필터용 배열 하나, 리턴용 배열 하나 따로만들고, 필터는 계속 쌓아가고, 리턴은 필터에 걸리면 그 값을 찾아 제거한다.
- 특정 값을 제거하는 타입을 만들어 제거한다. 못 찾으면 그대로 리턴하게 만들었다.
배운점
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>] : [];
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;
- 다음과 같이 return 값의 복사본을 가지고 있다가
- 필터를 통과 할 때는 복사본과 합치고,
- 필터를 통과 못 할 때는 복사본을 그대로 사용한다
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 }>>, ]
풀이
- 유니온은 중복을 알아서 제거하므로 경우의 수 다 넣음
배운점
// 틀린 답 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}
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>>, ]
풀이
number
의 경우를 걸러내야하므로number extends T
를 선행
- 템플릿 리터럴로 확인
배운점
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>>, ]
풀이
- 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>, ]
풀이
- 단순히 재귀적으로 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>>, ]
풀이
- 비즈니스로직은 어려운게 없었음
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
원시 타입 또는 유니온 원시 타입인
Predicate
과 Predicate
의 요소로 포함되는 배열을 반환하고, 배열 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]>>, ]
풀이
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]>>, ]
풀이
- 일일이 배열 돌면서 풀이하기에는 너무 많은 추가적인 타입이 필요할 것 같다는 생각이 들어서 패턴 매칭을 하되, 문자 하나 씩 확인하도록 함 (마지막 케이스 때문)
I
를 통해 현재 index 확인,C
를 통해 결과 누적
P
가 빈 문자열일 때 처리
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>
답지를 보고 개선점 두가지를 보았다.
infer _
대신string
으로 하는게 더 나았던 것 같다.
- 직접 리턴이 아니라 인자로 넘기는 것이라서 직접 리턴이 되는 형태로 추가하기 보단 인자에 추가하는 방식이 더 나았던 것 같다.
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개를 유니온 형태로 뿌려주면 되므로
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]>>, ]
풀이
I
는T
기준 재귀 할 때, 계속 같은 크기의 배열을 넣어 줄 예정이므로 브레이크 포인트를 잡기 위해 사용
- 순서를 바꿔가며 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']>>, ]
풀이
- 확인한 원소를 저장하고(
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]]>>, ]
풀이
I
는 원본 행렬 기준 행,J
는 열의 인덱스
C
는 1차원 배열,I
를 순회하면서 쌓을 배열
T
는 2차원 배열, 리턴할 전치행렬,C
를 쌓을 배열
- 기존
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;
풀이
- 설계문제라고 생각했었음
ObjectMetadata
타입을 통해 필요한 속성에 접근하려고 시도했으나 결국 필요 없었음. 그냥 확인용으로 유지
- 각 메타 데이터들이 존재 할 때 구분해야할 로직이 다르므로, 메타 데이터의 기준이 되는 속성 존재 여부로 처리 방법 다르게 나눔
- required 의 경우
Omit
을Wrapper
로 사용해 해결(답안 참고)
배운점
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>>, ]
풀이
I
와J
두개의 인덱스를 돌며,J
가N
길이가 만족이 되면,I
를 기준으로C
에J
를 추가하는 로직을 사용해 추가하여 배열 길이를 리턴
- 100 이상의 엄청 큰 수는 문제에 대한 항의 표시로 하드코딩 함
배운점
- 배열의 길이가 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>>, ]
풀이
I
를 하나씩 늘려가며C
에I
누적함
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]>>, ]
풀이
T
와U
가 유니온으로 들어오기 때문에 조건부 연산을 통해 분배 조건부 연산으로 동작하도록 하고, 중첩하여[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 }>>, ]
풀이
- 두개의 object 를 머지하는 타입을 우선 만듬.
- 두개 object 를 머지할 때는
T
의 key 인K
가U
의 key 의 부분집합인지 확인하여 부분집합이면 유니온으로 두개 합치고, 아니면 하나만 하도록 한다. 그리고, 그 반대도 해주어야 한다. 그리고 후처리로Omit
으로 감싸&
연산을 하여 정확히 맞지 않는 타입을 정리해준다.
- 그리고, 이를 각 배열 순회 하며 재귀로 쌓아감
배운점
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 membertype 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>>, ]
풀이
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 }>>, ]
풀이
- key 를 순회하며
_
가 앞에 붙은 경우 key 를 제거
배운점
- key 에
_
를 넣으면 private 타입으로 하는 관용적 표현이 있다는 것 상기, private 타입은 제거하는 문제였음
- 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>>, ]
풀이
- key 순회하며,
U
인 것은 제거
- 그리고
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 type
DeepOmit
, 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: {} } }>>, ]
풀이
U
를.
기준으로 나누어 재귀
- key 를 순회하며
DeepOmit
으로 해당 키 재귀
- 마지막
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>>, ]
풀이
- 홀수 여부는 마지막 자릿수만 판별하면 되므로, 예외 케이스 이외의 정수는 마지막 자릿수만 number 형태로 얻도록 하는 타입을 통해 마지막 자자릿수만 얻고자 함
- 각 수를 문자열 변환해 잘라내고 뒤집은 후,
[0]
을 통해 마지막 자릿수를 얻어냄
- 마지막 자리수는 Flag 를 통해 배열을 늘리며 재귀가 끝났을 때 플래그 리턴하는 방식으로 홀수인지 판별
- 소수점을 가진 수는 홀수가 아니므로
false
- 지수 표현은 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']]>>, ]
풀이
Hanoi<1>
은 A → B로 옮기면 된다.From
,To
를 직접 옮길 수 있다.
Hanoi<2>
는 가장 작은 것을 우선 A → C 로 옮기고, 이후 1개 인 것 처럼 A → B 로 옮기고, C 로 옮겨두었던 것을 C → A 로 옮기는 과정이 있다.
Hanoi<3>
은 우선 두 원판을 C 로 옮긴 후 (Hanoi<2, A, C, B>
), 1개 인 것 처럼 A → B 로 옮기고, C 로 옮겨 두었던 두 원판을 B 로 옮기는 과정이 있다. (Hanoi<2, C, B, A>
)
- 이를 통해,
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], ] > >, ]
풀이
- 기본적인 아이디어는 일반적인 풀이인 이중 포문 형식으로 해결하려고 함 + 테스트 케이스의 수가 적으므로, 작은 숫자 덧셈기로 직접 더하려고 함
I
는 행,J
는 열을 채울 인덱스,C
는 파스칼 삼각형의 한 depth 를 채울 배열,A
는C
를 누적해 쌓을 정답 2차원 배열
- 를 인덱스라고 하면, 에 따라서 번째 행의 번째 열에 있는 수와 번째 열에 있는 수를 더해야한다. 하지만, 어떤 수에서 -1 을 해주는 타입을 구현하여 풀이 과정에 사용하면 형식 인스턴스 깊이 에러가 나서 다음과 같은 방식으로 우회 했음.
- 실제 인덱스는
I
,J
배열에 원소 하나를 추가한 것을 사용하고, 번째 행과 번째 열에 접근 하기 위해 -1 인 경우를I
,J
원본 배열을 사용하여 인덱스에 접근
- 이렇게 하면 초기 값을 반복문에서 제공할 수 없으므로, 초기 값
C
와A
에 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
풀이
{} extends Record<S, 1>
을 통해 고정되지 않은 대부분의 케이스를 거를 수 있다.
- 이후,
Equal<[S], S extends unknown ? [S] : never>
를 통해 유니온이 포함된 케이스를 거른다.
배운점
Record<S, 1>
에서S
가 primary type 등 고정된 string 이 아니라면, 인덱스 시그니처 중 하나로 추가가 될 뿐이라서,{} extends Record<S, 1>
이 true 가 된다.
`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>>, ]
풀이
- 두 배열을 각각 잘라가며 먼저 전부 잘리는 경우 특정 케이스 리턴
- 두 배열의 길이가 같은 경우 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>>
풀이
- 조합 형태로 풀어내야 하므로,
Keys extends any
와keyof 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
로 넘길 때, 일반적으로 P
가 unknown
으로 추론되기 때문이다.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'>>, ]
풀이
- 가장 첫번째 문자를 기준으로 앞으로 하나씩 쌓아가며 포함하는지 확인한다.
Std
는 자르기 위한 기준 문자열,P
는 잘린 앞 문자열,Answer
는 정답이 될 문자열
-
Std
가‘’
가 아니라면 다음과 같이 실행한다. - 모든
T
안 문자열이P
를 포함한다면 → 재귀 - 포함하지 않는다면
Answer
(P
에서 하나 제거된 상태) 리턴
Std
가‘’
라면 다음과 같이 실행한다.- 모든
T
안 문자열이P
를 포함한다면 →P
리턴 - 포함하지 않는다면
‘’
리턴 (완전 찾지 못한 경우)
배운점
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'>>, ]
풀이
- 이중 배열을 순회 하며,
T[Y]
를 순회 할 때는X
와Y
가 같은 경우만을 남긴다.
ValueOf<X>
타입은 객체의 value 의 타입을 유니온 형태로 리턴한다.
- 그러므로, 외부 순회는
{0: T[0][0]} | {1: T[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>>, ]
풀이
- 알파벳이라면, 대문자화, 소문자화 했을때 자기 자신과 비교하면 두 조건 중 하나는 false 가 나올 수 밖에 없다.
- 둘 다 같은 경우
false
나머지는 true