주어진 배열을 특정 길이의 청크로 나누는 문제입니다.
lodash
를 아시나요? Chunk
는 lodash에서 매우 유용한 함수입니다. 이제 이것을 구현해보겠습니다.
Chunk<T, N>
는 두 개의 필수 타입 매개변수를 받습니다. T
는 반드시 튜플
이어야 하고, N
은 반드시 1 이상의 정수
여야 합니다.
예시
type exp1 = Chunk<[1, 2, 3], 2>; // 예상 결과: [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4>; // 예상 결과: [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1>; // 예상 결과: [[1], [2], [3]]
문제 설명
접근 방법
코드
type Chunk<
T extends any[],
N extends Number,
Result extends any[] = [],
Chunk extends any[] = []
> = T extends [infer First, ...infer Rest]
? Chunk["length"] extends N
? Chunk<Rest, N, [...Result, Chunk], [First]>
: Chunk<Rest, N, Result, [...Chunk, First]>
: Chunk["length"] extends 0
? Result
: [...Result, Chunk];
코드 설명
Result
는 최종적으로 반환될 배열(Chunk[]
)Chunk
는 청크를 채워나가는 배열T
를 재귀적으로 탐색하면서, Chunk
에 채워넣기Chunk
의 길이가 N
과 같아지면, Result
에 추가Fill
은 JavaScript의 일반적인 함수입니다. 이제 타입으로 이를 구현해보겠습니다.
Fill<T, N, Start?, End?>
에서 볼 수 있듯이, Fill
은 네 가지 타입의 매개변수를 받습니다. 이 중 T
와 N
은 필수 매개변수이고, Start
와 End
는 선택적 매개변수입니다.
이러한 매개변수들의 요구사항은 다음과 같습니다: T
는 반드시 tuple
이어야 하고, N
은 어떤 타입의 값이든 될 수 있으며, Start
와 End
는 0보다 크거나 같은 정수여야 합니다.
예시
type exp = Fill<[1, 2, 3], 0>; // 예상 결과: [0, 0, 0]
문제 설명
Fill
함수를 타입으로 구현Fill
함수는 배열 T
와 값 N
을 받아서, 배열의 일부를 N
으로 채우는 함수Start
와 End
는 선택적 매개변수로, 채우기를 시작할 인덱스와 끝날 인덱스를 지정접근 방법
N
으로 채우기Start
와 End
를 지정하지 않으면, 배열의 처음부터 끝까지 채우기Index
배열을 만들어서 체크Index
배열의 길이가 Start
와 같아지면, Switch
를 true
로 변경Index
배열의 길이가 End
와 같아지면, Switch
를 false
로 변경End
도달하기 전에 T
가 끝나면 Result
반환코드
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T["length"],
Index extends unknown[] = [],
Switch extends boolean = false,
Result extends any[] = []
> = T extends [infer First, ...infer Rest]
? Switch extends true
? // Switch가 true인 경우(Index가 End보다 클 경우)
Index["length"] extends End
? // 끝났으면 남은 배열 추가(Index가 End와 같아지면 끝나는 것이므로)
[...Result, ...T]
: // 아직 끝나지 않았으면 다음 요소 추가
Fill<Rest, N, Start, End, [...Index, unknown], Switch, [...Result, N]>
: // Switch가 false인 경우(Index가 Start보다 작을 경우)
Index["length"] extends Start
? // Switch가 false인데 Start와 같아졌을 경우
Start extends End
? // Start와 End가 같으면 N으로 변경 X(원래 값 추가)
[...Result, First, ...Rest]
: // Start와 End가 같지 않으면 N으로 변경, Switch를 true로 변경
Fill<Rest, N, Start, End, [...Index, unknown], true, [...Result, N]>
: // Switch가 false인데 Start보다 작지 않을 경우 그냥 진행
Fill<Rest, N, Start, End, [...Index, unknown], Switch, [...Result, First]>
: Result;
코드 설명
T
를 재귀적으로 탐색하면서 First
요소 확인Switch
가 true
인 경우,Index
배열의 길이가 End
와 같아지면 끝나는 것이므로 남은 배열 추가Switch
가 false
인 경우,Index
배열의 길이가 Start
와 같아지면 채워야되는 요소가 되므로 N
으로 변경, Switch
를 true
로 변경End
도달하기 전에 T
가 끝나면 Result
반환문자열 타입을 받아 끝부분의 공백을 제거한 새로운 문자열을 반환하는 TrimRight<T>
를 구현하세요.
예시
type Trimed = TrimRight<" Hello World ">; // expected to be ' Hello World'
문제 설명
접근 방법
코드
type TrimRight<S extends string> = S extends `${infer T}${" " | "\n" | "\t"}`
? TrimRight<T>
: S;
코드 설명
S
를 재귀적으로 탐색하면서 T
요소 확인T
요소가 whitespaces(' ' | '\n' | '\t')
가 들어가는 지 확인whitespaces
가 들어가면 재귀whitespaces
가 들어가지 않으면 그냥 반환Lodash의 without 함수를 타입 버전으로 구현하세요. Without<T, U>
는 배열 T
와 숫자 또는 배열 U
를 받아서 U
의 요소들을 제외한 배열을 반환합니다.
예시
type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
문제 설명
T
와 숫자 또는 배열 U
를 받아서 U
의 요소들을 제외한 배열을 반환U
는 숫자 또는 배열이 들어올 수 있음접근 방법
T
를 재귀적으로 탐색하면서 U
의 요소들을 제외한 배열을 반환코드
type TupleToUnion<T extends readonly number[] | number> = T extends number[]
? T[number]
: T;
type Without<T extends any[], U extends number | number[]> = T extends [
infer First,
...infer Rest
]
? First extends TupleToUnion<U>
? [...Without<Rest, U>]
: [First, ...Without<Rest, U>]
: T;
코드 설명
TupleToUnion<U>
는 U
가 튜플일 경우 유니온 타입으로 변환Without<T, U>
는 배열 T
를 재귀적으로 탐색하면서 U
의 요소들을 제외한 배열을 반환문자열이나 숫자를 받아서 소수점 이하 자릿수를 제거하여 정수 부분만 반환하는 Math.trunc
의 타입 버전을 구현하세요.
예시:
type A = Trunc<12.34>; // 12
접근 방법
number
일 경우 string
으로 변환코드
type Trunc<
T extends number | string,
S = `${T}`
> = S extends `${infer First}.${infer _}` ? `${First}` : S;
실패 원인
.3
, -.3
등의 예외 케이스 처리 실패접근 방법
infer
로 추론한 First
가 빈 문자열이거나 -
일 경우 0으로 처리코드
type Trunc<
T extends number | string,
S = `${T}`
> = S extends `${infer First}.${infer _}`
? First extends "" | "-"
? `${First}0`
: First
: S;
코드 설명
S
는 T
를 문자열로 변환한 결과S
가 .
를 포함할 경우, First
가 빈 문자열이거나 -
일 경우 First
+0
으로 처리First
를 그대로 반환S
가 .
를 포함하지 않을 경우, S
를 그대로 반환배열의 Array.indexOf
메서드의 타입 버전을 구현하세요. indexOf<T, U>
는 배열 T
와 임의의 값 U
를 받아서 배열 T
에서 처음 등장하는 U
의 인덱스를 반환합니다.
type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
type Res1 = IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>; // expected to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
접근 방법
index
를 계산하는 어레이를 하나 둬서 length
로 어레이 값 리턴infer
로 하나씩 확인하며 index Array
에 요소 하나씩 증가코드
type IndexOf<T extends any[], U, IndexArr extends unknown[] = []> = T extends [
infer First,
...infer Rest
]
? First extends U
? IndexArr["length"]
: IndexOf<Rest, U, [...IndexArr, unknown]>
: -1;
실패 원인
number
와 같은 타입, 그리고 any
를 제대로 처리하지 못함First
extends U
대신 더 강력한 타입비교를 만들어야 할 듯접근 방법
MyEqual
타입을 가져와서 First extends U
대신 사용코드
type MyEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type IndexOf<T extends any[], U, IndexArr extends unknown[] = []> = T extends [
infer First,
...infer Rest
]
? MyEqual<First, U> extends true
? IndexArr["length"]
: IndexOf<Rest, U, [...IndexArr, unknown]>
: -1;