[TS] Type-Challenges 스터디 13주차
![[TS] Type-Challenges 스터디 13주차](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2F19604ad2-3ac0-4af3-a52e-f9c1d88f7eaf%2Fimage.jpg&w=3840&q=75)
[medium] 5310. Join
View on GitHub: https://tsch.js.org/5310
문제
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'
문제 설명
Array.join()메서드를 타입 레벨에서 구현- 배열
T와 문자열 또는 숫자U를 받아서 배열T의 요소를U로 구분하여 문자열로 반환
제한 사항
- 배열
T의 요소 타입은 문자열 또는 숫자 U는 문자열 또는 숫자
시도 1
접근 방법
- 배열에서 하나씩 순회하면서, 마지막만 아니면 U 끼워넣어서 리턴하기
코드
type Join<
T extends (string | number)[],
U extends string | number,
> = T extends [
infer First extends string | number,
...infer Rest extends (string | number)[],
]
? T["length"] extends 1
? `${First}`
: `${First}${U}${Join<Rest, U>}`
: "";
실패 원인
- U가 존재하지 않는 경우 존재
시도 2 (정답)
접근 방법
U가 존재하지 않으면 기본값으로,추가
코드
type Join<
T extends (string | number)[],
U extends string | number = ",",
> = T extends [
infer First extends string | number,
...infer Rest extends (string | number)[],
]
? T["length"] extends 1
? `${First}`
: `${First}${U}${Join<Rest, U>}`
: "";
코드 설명
T를 infer로 분리하여 첫번째 요소와 나머지 요소로 분리T["length"]가 1이면 마지막 요소이기 때문에, 그냥 반환- 아닐 경우, 첫번째 요소와
U를 묶어서 재귀 호출
[medium] 5317. Last Index Of
View on GitHub: https://tsch.js.org/5317
문제
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
문제 설명
Array.lastIndexOf()메서드를 타입 레벨에서 구현- 배열
T와 값U를 받아서, 배열의 마지막U의 인덱스를 반환
제한 사항
T는 배열U는 배열의 요소 타입
시도 1 (정답)
접근 방식
- 기존에 사용한
indexof로직을 활용 - index를 만날 때마다
result에 저장 - 마지막까지 다 돌고 나서
result반환
코드
type MyEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type LastIndexOf<
T extends any[],
U,
IndexArr extends unknown[] = [],
Result = -1,
> = T extends [infer First, ...infer Rest]
? MyEqual<First, U> extends true
? LastIndexOf<Rest, U, [...IndexArr, unknown], IndexArr["length"]>
: LastIndexOf<Rest, U, [...IndexArr, unknown], Result>
: Result;
코드 설명
T를 infer로 분리하여 첫번째 요소와 나머지 요소로 분리MyEqual을 통해 첫번째 요소와U를 비교- 같으면
IndexArr의 길이를Result에 저장 - 재귀를 돌 때마다
IndexArr에unknown추가(IndexArr의 길이가 곧 해당 요소의index) - 마지막까지 돌고 나서
Result반환
[medium] 5360. Unique
View on GitHub: https://tsch.js.org/5360
문제
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]
문제 설명
Lodash.uniq메서드를 타입 레벨에서 구현- 배열
T를 받아서, 배열의 중복된 값을 제거한 배열을 반환
시도 1
- 배열을 순회하면서, 요소가 중복되는지 확인
- 중복되지 않으면 중복 체크용 유니온에 해당 요소 추가
코드
type Unique<
T extends any[],
Exists = never,
Result extends any[] = [],
> = T extends [infer First, ...infer Rest]
? First extends Exists
? Unique<Rest, Exists, Result>
: Unique<Rest, Exists | First, [...Result, First]>
: Result;
실패 이유
any등의 타입은extends로 비교할 수 없음.- 따라서
MyEqual타입을 만들어서 비교해야 함.
시도 2
접근 방식
MyEqual타입을 만들고, 유니온 각 요소를 확인하면서MyEqual이 단 하나라도true인지 확인
코드
type MyEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
type MyExtends<T, Exists extends any[]> = Exists extends [
infer First,
...infer Rest,
]
? MyEqual<T, First> extends true
? true
: MyExtends<T, Rest>
: false;
type Unique<
T extends any[],
Exists extends any[] = [],
Result extends any[] = [],
> = T extends [infer First, ...infer Rest]
? MyExtends<First, Exists> extends true
? Unique<Rest, Exists, Result>
: Unique<Rest, [...Exists, First], [...Result, First]>
: Result;
코드 설명
MyEqual타입을 만들고, 유니온 각 요소를 확인하면서MyEqual이 단 하나라도true인지 확인- 만약
true라면 무시하고 나머지 재귀호출 - 만약
false라면 해당 요소를 유니온에 추가하고 나머지 재귀호출 - 마지막에
Result반환
[medium] 5821. Map Types
View on GitHub: https://tsch.js.org/5821
문제
Implement MapTypes<T, R> which will transform types in object T to different types defined by type R which has the following structure
type StringToNumber = {
mapFrom: string; // value of key which value is string
mapTo: number; // will be transformed for number
};
Examples:
type StringToNumber = { mapFrom: string; mapTo: number };
MapTypes<{ iWillBeANumberOneDay: string }, StringToNumber>; // gives { iWillBeANumberOneDay: number; }
// Be aware that user can provide a union of types:
type StringToNumber = { mapFrom: string; mapTo: number };
type StringToDate = { mapFrom: string; mapTo: Date };
MapTypes<{ iWillBeNumberOrDate: string }, StringToDate | StringToNumber>; // gives { iWillBeNumberOrDate: number | Date; }
// If the type doesn't exist in our map, leave it as it was:
type StringToNumber = { mapFrom: string; mapTo: number };
MapTypes<
{ iWillBeANumberOneDay: string; iWillStayTheSame: Function },
StringToNumber
>; // // gives { iWillBeANumberOneDay: number, iWillStayTheSame: Function }
문제 설명
MapTypes<T, R>타입은 객체T의 타입을 변환하는 타입R은 변환 규칙을 정의하는 타입R의 구조는 다음과 같음 (mapFrom과mapTo가 있음)R은 여러 개의 타입을 유니온으로 받을 수 있음R에 없는 타입은 그대로 반환
시도 1
- 객체
T를 순회하면서,T[K]가R의mapFrom과 같은 타입인지 확인 - 같으면
mapTo타입으로 변환 - 같지 않으면 그대로 반환
코드
type MyEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false;
// mapFrom과 mapTo를 어떻게 꺼내오지?
// type MapTypes<T, R> = {
// [K in keyof T]: R extends R
// ? MyEqual<R["mapFrom"], K> extends true
// ? R["mapTo"]
// : T[K]
// : T[K];
// };
// infer로 mapFrom과 mapTo를 꺼내옴
type MapTypes<T, R> = {
[K in keyof T]: R extends { mapFrom: infer From; mapTo: infer To }
? T[K] extends From
? To
: T[K]
: never;
};
실패 이유
- 마지막 예제 실패
type example = MapTypes<
{ name: string; date: Date },
{ mapFrom: string; mapTo: boolean } | { mapFrom: Date; mapTo: string }
>;
- R 모두를 돌면서 각각의 결과의 유니온이 들어옴
시도 2 (답지 확인)
접근 방식
- 유니온 타입을 순회하면서 변환한 것, 변환하지 않은 것이 유니온으로 들어오지 못하게 처리
코드
type MapTypes<T, R extends { mapFrom: any; mapTo: any }> = {
[K in keyof T]: T[K] extends R["mapFrom"]
? R extends { mapFrom: T[K] }
? R["mapTo"]
: never
: T[K];
};
코드 설명
- 우선 R을 mapFrom과 mapTo를 가진 객체 타입으로 제한
- T[K]와 R["mapFrom"]를 비교
- 만약 extends가 가능하다면, R 중 mapFrom이 T[K]와 일치하는 요소만 남김(만약 extends가 불가능하다면, never 반환)
- 남은 요소의
mapTo타입으로 변환 - 만약
extends가 불가능하다면,T[K]그대로 반환
[medium] 7544. Construct Tuple
View on GitHub: https://tsch.js.org/7544
문제
Construct a tuple with a given length.
type result = ConstructTuple<2>; // expect to be [unknown, unkonwn]
문제 설명
- 주어진 길이의 튜플을 생성
- 주어진 길이만큼
unknown타입의 요소를 가진 튜플을 반환
시도 1 (정답)
접근 방식
- 재귀를 통해 튜플을 생성
Result의length가L이 될 때까지 재귀 호출
코드
type ConstructTuple<
L extends number,
Result extends unknown[] = [],
> = Result["length"] extends L
? Result
: ConstructTuple<L, [...Result, unknown]>;
코드 설명
Result의length가L이 될 때까지 재귀 호출Result의length가L이 되면 튜플을 반환
의문점
- ts의 재귀 깊이는 1000인가? 다른 문제에서는 100정도만으로도 터지던데... 정확한 매커니즘을 알고 싶다.
[medium] 8640. Number Range
View on GitHub: https://tsch.js.org/8640
문제
Sometimes we want to limit the range of numbers...
type result = NumberRange<2, 9>; // | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
문제 설명
- 주어진 범위의 숫자를 반환
시도 1
접근 방식
- L만큼의 길이를 가진 arr를 만들고, 그 길이를 계속 늘려가며 그 길이를 배열로 모아주자.
코드
type ConstructTuple<
L extends number,
Result extends unknown[] = [],
> = Result["length"] extends L
? Result
: ConstructTuple<L, [...Result, unknown]>;
type NumberRange<
L extends number,
H extends number,
Arr extends unknown[] = ConstructTuple<L>,
> = Arr["length"] extends H
? Arr["length"]
: Arr["length"] | NumberRange<L, H, [...Arr, unknown]>;
실패 이유
- 140 이상의 숫자를 넣으면 터짐
- 재귀 깊이 확인 필요
시도 2
접근 방식
ConstructTuple을 사용하지 않고, 재귀를 통해 범위의 숫자를 반환
코드
type NumberRange<
L extends number,
H extends number,
Arr extends unknown[] = [],
IsCount = false,
> = Arr["length"] extends H
? H
: IsCount extends true
? Arr["length"] | NumberRange<L, H, [...Arr, unknown], IsCount>
: Arr["length"] extends L
? L | NumberRange<L, H, [...Arr, unknown], true>
: NumberRange<L, H, [...Arr, unknown], false>;
실패 이유
- 얘도 140 이상의 숫자를 넣으면 터짐
시도 3 (답지 확인)
접근 방식
- Utils로 L 길이 만큼의 배열을 만듦(0, 1, 2, ...)
- L 길이와 H 길이의 배열에서 Exclude를 통해 L 이상 H 미만의 값만 남김
- 이후 유니온으로 묶어서 반환
코드
type Utils<L, C extends any[] = [], R = L> = C["length"] extends L
? R
: Utils<L, [...C, 0], C["length"] | R>;
type NumberRange<L, H> = L | Exclude<Utils<H>, Utils<L>>;
type example = NumberRange<2, 9>;
코드 설명
Utils는 재귀를 통해 L 길이 만큼의 배열을 만듦C["length"] extends L이라면,C의 길이가L에 도달- 그 때까지
R에는0부터L까지의 값이 유니온으로 관리되고 있음 - 이후
Exclude를 통해L이상H미만의 값만 남김 - 이후 유니온으로 묶어서 반환
comments
loading…