icon

메티의 블로그

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

12주차 타입챌린지 스터디

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

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 으로 변경하는 방식
  • 배운 점
    • 특정 배열을 변경하고 순회하는 것은 아래와 같이 할 수도 있다.
      • 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