[TS] Type-Challenges 스터디 11주차
![[TS] Type-Challenges 스터디 11주차](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2Ffa7d44aa-a8d8-4b16-b19b-fd5125283d2f%2Fimage.jpg&w=3840&q=75)
[medium] 4179. Flip
문제
객체의 키와 값을 서로 바꾸는 타입을 구현하세요.
제약사항
- 객체의 값은 string, number, boolean 타입만 가능합니다.
- 키가 될 수 있는 타입은 string, number, symbol만 가능하므로, 새로운 키는 반드시 문자열로 변환되어야 합니다.
- 중첩된 객체는 지원하지 않습니다.
- 배열과 같이 객체의 키가 될 수 없는 값들은 지원하지 않습니다.
예시
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"}
시도 1
접근 방식
- 우선 객체의 키 값들을 keyof로 순회한다.
- 순회한 키 값으로 T[K]를 키 값으로, K를 value 값으로 넣으면 되지 않을까?
코드
type Flip<T> = { [K in keyof T as T[K] extends PropertyKey ? T[K] : never]: K };
실패 이유
- boolean 값이 키 값으로 안들어가지는 이슈가 있음
- PropertyKey는 string, number, symbol 타입만 가능하므로, boolean 값이 키 값으로 들어가지 않음
시도 2 (정답)
접근 방식
- 1번 시도와 마찬가지로 접근
- 키 값을
PropertyKey로 제한하는 대신,string,number,boolean타입을 키 값으로 사용할 수 있도록 제한 - 이후 키 값을 문자열로 변환하여 사용
코드
type Flip<T> = {
[K in keyof T as T[K] extends string | number | boolean
? `${T[K]}`
: never]: K;
};
코드 설명
T[K] extends string | number | boolean형태로 키 값을 제한- 이후 키 값을 문자열로 변환하여 사용
- 기존 키 값은 밸류 값으로 사용
[medium] 4182. Fibonacci Sequence
문제
숫자 T를 입력받아 해당하는 피보나치 수를 반환하는 제네릭 타입 Fibonacci<T>를 구현하세요.
수열은 다음과 같이 시작됩니다:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
예시
type Result1 = Fibonacci<3>; // 2
type Result2 = Fibonacci<8>; // 21
시도 1 (정답)
접근 방식
- 재귀를 통해 피보나치 수열을 구현
- 숫자 더하기 연산이 안되니까 배열에 뭔가를 넣고, 그 배열의 length를 이용해보자
코드
type Fibonacci<
T extends number,
Fibo1 extends any[] = ["f"],
Fibo2 extends any[] = ["f"],
Count extends any[] = ["f", "f", "f"]
> = T extends 1 | 2
? 1
: Count["length"] extends T
? [...Fibo1, ...Fibo2]["length"]
: Fibonacci<T, Fibo2, [...Fibo1, ...Fibo2], [...Count, "f"]>;
코드 설명
T extends 1 | 2형태로 초기 조건 설정(T가 1, 2일 때 항상 1)T가 3 이상일 때Count의length가T와 같아지면 앞의 두 배열을 합한 배열의length를 반환- 만약
Count의length가T보다 작으면, 뒤의 배열을 앞의 배열에 할당하고, 뒤의 배열은 앞의 배열과 뒤의 배열을 합친 배열을 할당 - 이후
Count에"f"를 추가하여 재귀 호출
[medium] 4260. All Combinations
문제
문자열 S의 문자들을 최대 한 번씩만 사용하여 만들 수 있는 모든 문자열 조합을 반환하는 AllCombinations<S> 타입을 구현하세요.
예시
type AllCombinations_ABC = AllCombinations<"ABC">;
// 결과값: '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
시도 1
접근 방법
- 우선 문자열을 union으로 변환
- union을 기반으로 permutation 생성
- 생성된 요소들을 다시 합쳐 string으로 변환
코드
type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
? [First, ...StringToTuple<Last>]
: [];
type StringToUnion<S extends string> = StringToTuple<S>[number];
type Permutation<T extends string[], K = T> = [T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: never;
type Join<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
]
? `${First}${Join<Rest>}`
: "";
type AllCombinations<
S extends string,
P = Permutation<StringToUnion<S>>
> = P extends P ? Join<P> : never;
코드 설명
StringToTuple함수는 문자열을 튜플로 변환StringToUnion함수는 튜플을 union으로 변환Permutation함수는 주어진 배열의 순열을 생성Join함수는 배열을 문자열로 결합
실패 이유
Permutation을 활용하면 안됨- 왜냐하면 모든 요소를 사용한 조합만 볼 수 있음
- 예를 들어
ABC의 경우ABC,ACB,BAC,BCA,CAB,CBA이렇게 6개의 요소만 확인 가능 - 하지만
Exclude를 활용해 접근하는 방식은 맞을 듯
시도 2 (답지 확인)
코드
type StringToUnion<S extends string> = S extends `${infer L}${infer R}`
? L | StringToUnion<R>
: S;
type Combination<A extends string, B extends string> =
| A
| B
| `${A}${B}`
| `${B}${A}`;
type UnionCombination<A extends string, B extends string = A> = A extends B
? Combination<A, UnionCombination<Exclude<B, A>>>
: never;
type AllCombinations<S extends string> = UnionCombination<StringToUnion<S>>;
코드 설명
StringToUnion함수는 문자열을 union으로 변환 ('' 포함)Combination함수는 두 문자열을 이용해 조합 생성UnionCombination함수는 string union으로 들어온A를 순회하며, 각 요소와 해당 요소를 제외한 union을 재귀적으로 호출하여 조합 생성AllCombinations함수는UnionCombination함수를 활용하여 모든 문자열 조합을 반환
새롭게 배운 점
StringToUnion함수 구현 방식
type StringToUnion<S extends string> = S extends `${infer L}${infer R}`
? L | StringToUnion<R>
: S;
- template literal의 infer 구문을 활용해 문자열을 분리하고, 이를 재귀적으로 처리
[medium] 4425. Greater Than
문제
이 챌린지에서는 T > U와 같은 GreaterThan<T, U> 타입을 구현해야 합니다.
음수는 고려하지 않아도 됩니다.
예시
GreaterThan<2, 1>; //true가 되어야 함
GreaterThan<1, 1>; //false가 되어야 함
GreaterThan<10, 100>; //false가 되어야 함
GreaterThan<111, 11>; //true가 되어야 함
시도 1 (정답)
접근 방법
- 두 숫자를 문자열로 변환하여 자릿수 비교
- 자릿수가 같다면 맨 앞글자에서부터 확인
- 자릿수가 다르다면 자릿수 비교
코드
type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
? [First, ...StringToTuple<Last>]
: [];
// N 크기의 unknown[] 생성
type BuildArray<
N extends number,
Arr extends unknown[] = []
> = Arr["length"] extends N ? Arr : BuildArray<N, [...Arr, unknown]>;
// T가 U 이상인지 확인(같거나 더 클 경우)
type CompareDigit<T extends number, U extends number> = BuildArray<T> extends [
...BuildArray<U>,
...infer Rest
]
? true
: false;
// 맨 앞의 0을 날려줘야 함
type StringToNumber<S extends string> =
S extends `${"0"}${infer Rest extends number}`
? Rest
: S extends `${infer N extends number}`
? N
: never;
// string[]을 합쳐 string으로 변환
type Join<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
]
? `${First}${Join<Rest>}`
: "";
type GreaterThan<
T extends number,
U extends number,
TupleT extends string[] = StringToTuple<`${T}`>,
TupleU extends string[] = StringToTuple<`${U}`>
> =
// 같은 값인지 비교
T extends U
? false
: // 두 숫자의 자릿수가 같은지 비교
TupleT["length"] extends TupleU["length"]
? TupleT["length"] extends 1
? // 자릿수가 모두 1일 경우, 한자리수 비교
CompareDigit<T, U>
: // 실제 앞에서부터 한글자씩 비교 로직
TupleT extends [
infer TFirst extends string,
...infer TRest extends string[]
]
? TupleU extends [
infer UFirst extends string,
...infer URest extends string[]
]
? TFirst extends UFirst
? GreaterThan<
StringToNumber<Join<TRest>>,
StringToNumber<Join<URest>>
>
: CompareDigit<StringToNumber<TFirst>, StringToNumber<UFirst>>
: true
: false
: // 자릿수가 다를 경우, length들끼리 비교
CompareDigit<TupleT["length"], TupleU["length"]>;
코드 설명
-
같은 값일 경우 false 리턴
-
이후 두 숫자의 자릿수가 같은지 비교
-
자릿수가 같고 한자리수일 경우, 두 숫자를 비교하여 결과 리턴
-
자릿수가 같고 한자리수가 아닐 경우, 재귀로 비교(앞의 한 자리씩)
-
자릿수가 다를 경우, 각 자릿수를 비교하여 결과 리턴
-
StringToTuple함수는 문자열을 튜플로 변환 -
BuildArray함수는 숫자에 따라 그 길이만큼의 unknown[] 타입을 생성 -
CompareDigit함수는 두 숫자를 비교하여 결과 리턴 -
StringToNumber함수는 문자열을 숫자로 변환 -
Join함수는 튜플을 문자열로 변환
[medium] 4471. Zip
문제
이 챌린지에서는 Zip<T, U> 타입을 구현해야 합니다. 여기서 T와 U는 반드시 Tuple 이어야 합니다.
예시
type exp = Zip<[1, 2], [true, false]>; // expected to be [[1, true], [2, false]]
문제 설명
- 이 문제는 두 개의 튜플을 받아서 각각의 같은 위치에 있는 요소들을 쌍으로 묶어 새로운 튜플을 만드는 타입을 구현
시도 1 (정답)
접근 방법
- 두 개의 튜플의 맨 앞자리를 infer로 추출해서, 둘 다 존재할 경우 새로운 튜플에 추가로 넣어주기
- 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복
코드
type Zip<T extends any[], U extends any[]> = T extends [
infer TFirst,
...infer TRest
]
? U extends [infer UFirst, ...infer URest]
? [[TFirst, UFirst], ...Zip<TRest, URest>]
: []
: [];
코드 설명
- 두 개의 튜플에 모두 첫번째 요소가 존재할 경우, 두 요소를 묶은 튜플을 만들고, 이후 재귀로 나머지 요소들에 대해서도 같은 작업을 반복
- 만약 둘 중에 하나라도 첫번째 요소가 비어있을 경우(길이가 다를 경우), 빈 배열을 반환하여 재귀 종료
[medium] 4484. IsTuple
문제
IsTuple이라는 타입을 구현하세요. 이 타입은 입력 타입 T를 받아서 T가 튜플 타입인지 여부를 반환합니다.
예시
type case1 = IsTuple<[number]>; // true
type case2 = IsTuple<readonly [number]>; // true
type case3 = IsTuple<number[]>; // false
문제 설명
- 이 문제는 입력 타입 T가 튜플인지 여부를 확인하는 타입을 구현하는 것입니다.
- 튜플은 고정된 길이를 가진 배열 타입으로, 각 요소의 타입이 개별적으로 정의될 수 있습니다.
- 일반 배열과 튜플을 구분할 수 있어야 합니다.
- readonly 튜플도 처리할 수 있어야 합니다.
시도1 (정답)
접근 방법
- 튜플은 고정된 길이를 가진 배열 타입이므로 readonly인지, 그리고 길이가 존재하는지 확인
코드
type IsTuple<T> = [T] extends [never]
? false
: T extends readonly any[]
? number extends T["length"]
? false
: true
: false;
코드 설명
- 우선 never인 경우 false 처리
- 그 다음, T가 readonly 배열인지 확인
- readonly 배열인 경우, 길이가 number인지 확인(길이를 확인할 수 있는지)
comments
loading…