Solving Type Challenge
I’ve been trying to solve some type challenges to understand typescript better.
I had a lot of fun with a few easy and medium challenges which involve implementing types.
One of which I really enjoyed implementing is IndexOf<T, U>
.
Basically it behaves like a regular indexOf
function, except we are dealing with types, not literal values
type A = IndexOf<[1, 2, 3], 2>; // A is 1
type B = IndexOf<[2, 6, 3, 7, 9, 3, 8, 10], 3>; // B is 2
type C = IndexOf<[0, 0, 0], 2>; // C is -1
I started with this:
type IndexOf<T extends any[], U> = T extends [U, ...any]?
0: (
T extends [...infer X, U, ...infer Y]?
X['length'] : (
T extends [...infer H, U]?
H['length'] : -1
)
)
Basically I was thinking along these lines,
- Try to detect U at the start of the the array-like structure represented by T
- If match, we return 0 else we try to detect U within T instead of at the beginning
- Else try to detect U at the end of T
It didn’t work. It took me a long time to realize (2) was impossible because if there are multiple U
in T, the type checker won’t be able to infer X
or Y
.
Next I thought of a recursive solution:
type IndexOf<T extends any[], U> = T extends [U, ...any]?
0: (
T extends [...infer X, U]?
X['length'] : (
T extends [any, ...infer M, any]?
1 + IndexOf<M, U> : -1
)
)
This didn’t even type check, because we are dealing with types and 1 + IndexOf<M, U>
is not valid. How can we get position if there’s no way to increment?
What if we start matching recursively from the end of T?
Third attempt:
type ExactlySame<C, P> = C extends P? (P extends C? true : false) : false
type IndexOf<T extends any[], U> =T extends [...any, infer G]? (
ExactlySame<G, U> extends true?
(T extends [...infer H, any]?
(IndexOf<H, U> extends -1? H['length']: IndexOf<H, U>): never
):
(T extends [...infer H, any]? IndexOf<H, U> : -1)) : -1
This worked! Whenever we matched U at the end of T, the index of U is the length of the number of elements before U. However we cannot immediately return that length, because U may occur earlier in T. So we recurse with the part before U, H
. If the chain of recursive calls leads to -1, we then accept the index of the later match.