[TS] Type-Challenges 스터디 10주차
![[TS] Type-Challenges 스터디 10주차](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2F7a979855-f641-47d5-b2b1-26dba9f213aa%2Fimage.jpg&w=3840&q=75)
[Medium] 3188. Tuple to Nested Object
문제
Given a tuple type T that only contains string type, and a type U, build an object recursively.
주어진 튜플 타입 T 가 문자열 타입만 포함하고 있고, 타입 U 가 주어졌을 때, 재귀적으로 객체를 만드세요.
예시
type a = TupleToNestedObject<["a"], string>; // {a: string}
type b = TupleToNestedObject<["a", "b"], number>; // {a: {b: number}}
type c = TupleToNestedObject<[], boolean>; // boolean. if the tuple is empty, just return the U type
시도 1
접근 방식
- 먼저 빈 배열 처리
- 재귀로 튜플의 First와 Rest를 나눠서
First: {Rest: U}이런 형태가 되도록 처리
코드
type TupleToNestedObject<T extends any[], U> = T extends []
? U
: T extends [infer First, ...infer Rest]
? {First: TupleToNestedObject<Rest, U>}
: never;
]
실패 이유
- 위 코드의
First: TupleToNestedObject<Rest, U>부분에서 사용된First가infer로 추론된First값이 아닌 문자열'First'로 추론됨
시도 2 (정답)
접근 방식
- 문제 조건에 따라 string으로 키값 제어, K라는 변수로 First를 키값으로 받게 추가
코드
type TupleToNestedObject<T extends string[], U> = T extends [
infer First extends string,
...infer Rest extends string[],
]
? { [K in First]: TupleToNestedObject<Rest, U> }
: U;
코드 설명
infer First extends string, ...infer Rest extends string[]형태로 튜플의 First와 Rest를 나눠서 재귀적으로 처리[K in First]: TupleToNestedObject<Rest, U>형태로 키값을 First로 받고, 나머지 부분을 재귀적으로 처리- 빈 튜플일 경우, U 타입을 그대로 반환
[Medium] 3192. Reverse
문제
튜플을 뒤집는 타입을 구현하세요.
예시
type a = Reverse<["a", "b"]>; // ['b', 'a']
type b = Reverse<["a", "b", "c"]>; // ['c', 'b', 'a']
시도 1 (정답)
접근 방식
- 재귀로 튜플의 First와 Rest를 나눠서 First를 맨 뒤로 보내는 방식
코드
type Reverse<T extends any[]> = T extends [infer First, ...infer Rest]
? [...Reverse<Rest>, First]
: T;
코드 설명
T extends any[]형태로 튜플 타입 제어T extends [infer First, ...infer Rest]형태로 튜플의 First와 Rest를 나눠서 재귀적으로 처리[...Reverse<Rest>, First]형태로 Rest를 뒤집은 결과(재귀)에 First를 추가- 빈 튜플일 경우, T 타입을 그대로 반환
[Medium] 3196. FlipArguments
문제
lodash의 _.flip 함수를 타입으로 구현하세요.
FlipArguments<T> 타입은 함수 타입 T를 요구하며, 동일한 반환 타입을 가지지만 매개변수가 반대로 된 새로운 함수 타입을 반환합니다.
예시
type Flipped = FlipArguments<
(arg0: string, arg1: number, arg2: boolean) => void
>;
// (arg0: boolean, arg1: number, arg2: string) => void
시도 1 (정답)
접근 방식
T의args를 순회하며 각각의 arg type을 저장한 뒤,reverse해서 다시 할당해보자.3192-reverse문제를 참고
코드
type Reverse<T extends any[]> = T extends [infer First, ...infer Rest]
? [...Reverse<Rest>, First]
: T;
type FlipArguments<T extends (...args: any[]) => any> = T extends (
...args: infer Args
) => infer ReturnType
? (...args: Reverse<Args>) => ReturnType
: never;
코드 설명
T extends (...args: any[]) => any형태로 함수 타입 제어...args: infer Args형태로 함수의 인자 타입을 추론 (인자는 튜플 타입으로 추론됨)(...args: Reverse<Args>) => ReturnType형태로 인자를 뒤집은 뒤, 반환 타입을 그대로 반환- 함수 타입이 아닌 경우,
never타입 반환
[Medium] 3243. FlattenDepth
문제
배열을 주어진 깊이까지 재귀적으로 평탄화하는 타입을 구현하세요.
예시
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>; // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]>; // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
시도 1 (정답)
접근 방식
- 기존 코드 활용(2257-minus-one, 459-flatten)
- N이 0이 될 때까지 flatten 함수를 재귀적으로 호출
- flatten은 459-flatten과 다르게 1회만 실행하도록 처리
코드
/* _____________ 기존 코드 활용 _____________ */
// 한 자리 수의 마이너스 1 처리용 타입
type CalcMinusOne = {
[key: string]: string;
"1": "0";
"2": "1";
"3": "2";
"4": "3";
"5": "4";
"6": "5";
"7": "6";
"8": "7";
"9": "8";
"0": "9";
};
// 문자열 배열(튜플)의 마지막 요소 추출
type Last<T extends string[]> = T extends [...infer _, infer Last]
? Last
: never;
// 문자열을 튜플로 변환
type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
? [First, ...StringToTuple<Last>]
: [];
// 튜플을 문자열로 변환
type Join<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[],
]
? `${First}${Join<Rest>}`
: "";
// 마지막 요소를 제거한 튜플을 문자열로 변환
type RemoveLast<T extends string[]> = T extends [
...infer Rest extends string[],
infer _,
]
? Join<Rest>
: "";
// 문자열을 숫자로 변환
type StringToNumber<S extends string> = S extends `${infer N extends number}`
? N
: never;
// 문자열 배열(튜플)의 마지막 요소가 0인 경우, 마지막 요소를 제거한 문자열 배열(튜플)을 문자열로 변환
// 만약 마지막 요소가 0이면 9로 변환, 그리고 앞 요소에 대해서도 재귀 돌리기
type MinusOneInStringArray<
S extends string,
ST extends string[] = StringToTuple<S>,
> =
Last<ST> extends "0"
? S extends "10"
? "9"
: `${MinusOneInStringArray<RemoveLast<ST>>}9`
: Last<ST> extends "_"
? `${MinusOneInStringArray<RemoveLast<ST>>}_`
: `${RemoveLast<ST>}${CalcMinusOne[Last<ST>]}`;
// 재귀 돌리기 전처리
type MinusOne<T extends number, S extends string[] = StringToTuple<`${T}`>> =
Last<S> extends "0"
? StringToNumber<`${MinusOneInStringArray<RemoveLast<S>>}9`>
: StringToNumber<`${RemoveLast<S>}${CalcMinusOne[Last<S>]}`>;
// Flatten 함수(1회만 실행하는)
type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
? First extends any[]
? [...First, ...Flatten<Rest>]
: [First, ...Flatten<Rest>]
: [];
/* _____________ Your Code Here _____________ */
type FlattenDepth<T extends any[], N extends number = 1> = N extends 0
? T
: FlattenDepth<Flatten<T>, MinusOne<N>>;
실패 이유
- 재귀 호출 횟수가 너무 많아서 런타임 오류 발생
- 재귀 호출 횟수를 제한하는 방법을 찾아야 함
시도 2 (정답)
접근 방식
- 만약 이미 flatten된 배열이면 depth가 남았어도 재귀 끝내기
코드
type FlattenDepth<T extends any[], N extends number = 1> = N extends 0
? T
: T extends (string | number | symbol)[]
? T
: FlattenDepth<Flatten<T>, MinusOne<N>>;
코드 설명
N extends 0형태로 N이 0이 될 때까지 재귀 호출T extends (string | number | symbol)[]형태로 이미 flatten된 배열인 경우 재귀 끝내기FlattenDepth<Flatten<T>, MinusOne<N>>형태로 flatten 함수를 재귀적으로 호출
더 나은 방법
- 빈 배열을 하나 생성 후, 재귀 호출할 때마다 배열에 요소를 추가해 1개씩 배열의 길이 늘리기
- 이후 주어진 N과 배열의 길이(
CountArray['length'])를 비교하여 재귀 호출 중단
[Medium] 3326. BEM-style-string
문제
Block, Element, Modifier 방법론(BEM)은 CSS에서 클래스 명명에 널리 사용되는 규칙입니다.
예를 들어, 블록 컴포넌트는 btn으로 표현되고, 블록에 종속된 엘리먼트는 btn__price로 표현되며, 블록의 스타일을 변경하는 수정자는 btn--big 또는 btn__price--warning으로 표현됩니다.
BEM<B, E, M>을 구현하세요. 이는 세 개의 매개변수로부터 문자열 유니온을 생성합니다. 여기서 B는 문자열 리터럴이고, E와 M은 문자열 배열입니다(비어있을 수 있음).
예시
type a = BEM<"btn", ["price"], []>; // 'btn__price'
시도 1 (정답)
접근 방식
- 주어진 E, M을 각각 순회하면서 합쳐서 보여줄 방법을 찾자.
- 문자열 관련된 문제이니, string 리터럴 타입을 고려해보자.
코드
type BEM<
B extends string,
E extends string[],
M extends string[],
> = `${B}${E extends [] ? "" : `__${E[number]}`}${M extends []
? ""
: `--${M[number]}`}`;
코드 설명
- E extends [] ? "" : __${E[number]} 형태로 E가 빈 배열인 경우 빈 문자열 반환, 비어있지 않을 경우 순회하여 유니온 요소 각각 생성
- 마찬가지로 M 또한 동일한 로직으로 작동
[Medium] 3376. InorderTraversal
문제
이진 트리의 중위 순회를 구현하는 타입을 작성하세요.
예시
const tree1 = {
val: 1,
left: null,
right: {
val: 2,
left: {
val: 3,
left: null,
right: null,
},
right: null,
},
} as const;
type A = InorderTraversal<typeof tree1>; // [1, 3, 2]
시도 1 (정답)
접근 방식
- 재귀를 통해 중위 순회를 구현하자.
- 중위 순회는 왼쪽, 밸류값, 오른쪽 순서로 순회.
코드
interface TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
}
type InorderTraversal<T extends TreeNode | null> = T extends TreeNode
? [...InorderTraversal<T["left"]>, T["val"], ...InorderTraversal<T["right"]>]
: [];
코드 설명
T extends TreeNode형태로 TreeNode 타입인 경우 재귀 호출T extends TreeNode | null형태로 TreeNode 타입이 아닌 경우 빈 배열 반환[...InorderTraversal<T["left"]>, T["val"], ...InorderTraversal<T["right"]>]형태로 왼쪽, 밸류값, 오른쪽 순서로 순회
comments
loading…