The best way to solve the TS problem is to practice more. This time, the difficulty of interpreting type-challenges Medium is 49~56 questions.
intensive reading
Flip
Implement Flip<T>
and swap the Key and Value in the object T
:
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'}
When describing the object in keyof
, you can add deformation through as
, so this question should be handled like this:
type Flip<T> = {
[K in keyof T as T[K]]: K
}
Since the Key position can only be String or Number, so T[K]
Description of Key will display an error, we need to limit the type of Value:
type Flip<T extends Record<string, string | number>> = {
[K in keyof T as T[K]]: K
}
But this answer fails the test case Flip<{ pi: 3.14; bool: true }>
because true
cannot be used as a Key. Only the string 'true'
can be used as the Key, so we have to forcibly convert the Key position into a string:
// 本题答案
type Flip<T extends Record<string, string | number | boolean>> = {
[K in keyof T as `${T[K]}`]: K
}
Fibonacci Sequence
Use TS to implement Fibonacci sequence calculation:
type Result1 = Fibonacci<3> // 2
type Result2 = Fibonacci<8> // 21
Since the test case does not have a particularly large Case, we can safely use recursive implementation. The JS version of Fibonacci is very natural, but in the TS version, we can only simulate the calculation with the length of the array, and the code will naturally be distorted.
First, an extra variable is needed to mark how many times the recursion is performed, and the recursion ends at the Nth time:
type Fibonacci<T extends number, N = [1]> = N['length'] extends T ? (
// xxx
) : Fibonacci<T, [...N, 1]>
Each time the above code is executed, it is determined whether the recursion is completed, otherwise the recursion is continued and the counter is incremented by one. We also need an array to store the answer and an array to store the previous number:
// 本题答案
type Fibonacci<
T extends number,
N extends number[] = [1],
Prev extends number[] = [1],
Cur extends number[] = [1]
> = N['length'] extends T
? Prev['length']
: Fibonacci<T, [...N, 1], Cur, [...Prev, ...Cur]>
递归时拿Cur
Prev
,用[...Prev, ...Cur]
Cur
,也就是说,下次的Cur
conforms to Fibonacci definition.
AllCombinations
Realize AllCombinations<S>
for string S
full permutation:
type AllCombinations_ABC = AllCombinations<'ABC'>
// should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
First of all, the ABC
string must be split into separate union types, and the second combination can complete the full arrangement:
type StrToUnion<S> = S extends `${infer F}${infer R}`
? F | StrToUnion<R>
: never
infer
When describing a string, the first one points to the first letter and the second one points to the remaining letters; the remaining strings can be recursively decomposed into single characters one by one and use |
connect:
StrToUnion<'ABC'> // 'A' | 'B' | 'C'
StrToUnion<'ABC'>
the result of ---25a88983295858a1d0ce6e39f8d2fd0b--- as U
, then using the object-to-join type feature, we can create the full arrangement of ABC
in three letters:
{ [K in U]: `${K}${AllCombinations<never, Exclude<U, K>>}` }[U] // `ABC${any}` | `ACB${any}` | `BAC${any}` | `BCA${any}` | `CAB${any}` | `CBA${any}`
However, as long as you cleverly add '' |
in each recursion, you can get the answer directly:
type AllCombinations<S extends string, U extends string = StrToUnion<S>> =
| ''
| { [K in U]: `${K}${AllCombinations<never, Exclude<U, K>>}` }[U] // '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
Why is it so amazing? This is because each recursion will go through the process of ''
, 'A'
, 'AB'
, 'ABC'
gradually accumulating characters each time. will encounter '' |
to form a joint type naturally. For example, when encountering 'A'
, it will naturally form 'A'
this joint type, and continue to use 'A'
is combined with Exclude<'A' | 'B' | 'C', 'A'>
.
More subtle is that the first execution ''
fills the first case of the full permutation.
Finally, I noticed that the above result produced an Error: "Type instantiation is excessively deep and possibly infinite", that is, the recursion may produce an infinite loop, because the result of ---04c26f2f68a4603d05036dfc9fa8a442 Exclude<U, K>
may be never
, So finally, at the beginning, we need to repair the judgment of never
. Using the knowledge learned before, never
will not perform joint type expansion, so we use [never]
to judge avoid:
// 本题答案
type AllCombinations<S extends string, U extends string = StrToUnion<S>> = [
U
] extends [never]
? ''
: '' | { [K in U]: `${K}${AllCombinations<never, Exclude<U, K>>}` }[U]
Greater Than
Implement GreaterThan<T, U>
judgment T > U
:
GreaterThan<2, 1> //should be true
GreaterThan<1, 1> //should be false
GreaterThan<10, 100> //should be false
GreaterThan<111, 11> //should be true
Because TS does not support addition and subtraction and size judgment, when you see this question, you should think of two ways. One is recursion, but it will be limited by the amount of input parameters, which may cause stack overflow, and the other is to refer to MinusOne 's A special method that constructs an array with the expected length in a clever way, and compares it with an array ['length']
.
Let’s talk about the first one first, recursion must have an incremental key, take T
U
to compare successively, whoever catches up with this number first is the smaller one:
// 本题答案
type GreaterThan<T, U, R extends number[] = []> = T extends R['length']
? false
: U extends R['length']
? true
: GreaterThan<T, U, [...R, 1]>
Another approach is to quickly construct two arrays with lengths equal to T
U
, and use the array to quickly determine who is longer. The construction method is no longer expanded, refer to the method of MinusOne
, and focus on how to quickly judge [1, 1]
and [1, 1, 1]
who is bigger.
Because TS does not have the ability to judge the size, it is useless to get ['length']
, we have to consider arr1 extends arr2
this way. Unfortunately, arrays with unequal lengths, extends
always equal to false
:
[1,1,1,1] extends [1,1,1] ? true : false // false
[1,1,1] extends [1,1,1,1] ? true : false // false
[1,1,1] extends [1,1,1] ? true : false // true
But we expect to make the following judgments:
ArrGreaterThan<[1,1,1,1],[1,1,1]> // true
ArrGreaterThan<[1,1,1],[1,1,1,1]> // false
ArrGreaterThan<[1,1,1],[1,1,1]> // false
The solution reflects the TS thinking: since the two arrays are equal to return true
, then we use [...T, ...any]
for supplementary judgment, if it can be determined as the former true
, The length is shorter (because the latter can be judged after adding several items):
type ArrGreaterThan<T extends 1[], U extends 1[]> = U extends [...T, ...any]
? false
: true
With that, the second answer goes like this:
// 本题答案
type GreaterThan<T extends number, U extends number> = ArrGreaterThan<
NumberToArr<T>,
NumberToArr<U>
>
Zip
Implement TS version Zip
function:
type exp = Zip<[1, 2], [true, false]> // expected to be [[1, true], [2, false]]
This problem also cooperates with auxiliary variables, performs counting recursion, and uses an additional type variable to store the result:
// 本题答案
type Zip<
T extends any[],
U extends any[],
I extends number[] = [],
R extends any[] = []
> = I['length'] extends T['length']
? R
: U[I['length']] extends undefined
? Zip<T, U, [...I, 0], R>
: Zip<T, U, [...I, 0], [...R, [T[I['length']], U[I['length']]]]>
[...R, [T[I['length']], U[I['length']]]]
Add a result according to the Zip rule in each recursion, of which I['length']
acts like the subscript i of the for loop, but in TS syntax, we can only use arrays way to simulate this count.
IsTuple
Implement IsTuple<T>
judgment T
whether it is a tuple type (Tuple):
type case1 = IsTuple<[number]> // true
type case2 = IsTuple<readonly [number]> // true
type case3 = IsTuple<number[]> // false
What I have to complain about is that whether it is TS internal or lexical analysis is a more effective way to judge, but if you use TS to implement it, you have to change your way of thinking.
The difference between Tuple and Array in TS is that the former has a limited length and the latter has an infinite length. From the result, if you access its ['length']
attribute, the former must be a fixed number, while the latter returns number
, use this feature to judge:
// 本题答案
type IsTuple<T> = [T] extends [never]
? false
: T extends readonly any[]
? number extends T['length']
? false
: true
: false
In fact, this answer is based on a single test. Because there is a single test case of IsTuple<{ length: 1 }>
99a6793f09101293d49e2083099e56d6---, it can pass the verification of number extends T['length']
, but because it is not an array type, it cannot be Through the pre-judgment of T extends readonly any[]
.
Chunk
Implement TS version Chunk
:
type exp1 = Chunk<[1, 2, 3], 2> // expected to be [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4> // expected to be [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1> // expected to be [[1], [2], [3]]
The old method is still recursive, which requires a variable to record the content currently collected in the Chunk, and release it when the Chunk reaches the upper limit.
type Chunk<
T extends any[],
N extends number = 1,
Chunked extends any[] = []
> = T extends [infer First, ...infer Last]
? Chunked['length'] extends N
? [Chunked, ...Chunk<T, N>]
: Chunk<Last, N, [...Chunked, First]>
: [Chunked]
Chunked['length'] extends N
judgment Chunked
array length reaches N
Chunked
then release it, otherwise put the first item in the current array First
Chunked
Array, the array items continue to recurse from Last
.
We found that Chunk<[], 1>
did not pass this single test, because when Chunked
has no items, there is no need to group, so the complete answer is:
// 本题答案
type Chunk<
T extends any[],
N extends number = 1,
Chunked extends any[] = []
> = T extends [infer Head, ...infer Tail]
? Chunked['length'] extends N
? [Chunked, ...Chunk<T, N>]
: Chunk<Tail, N, [...Chunked, Head]>
: Chunked extends []
? Chunked
: [Chunked]
Fill
To implement Fill<T, N, Start?, End?>
, replace each item of the array T
with N
:
type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]
This problem also needs to be solved by recursion + Flag, that is, define a I
to indicate the current recursive subscript, and a Flag
to indicate whether the subscript to be replaced has been reached, as long as it reaches this subscript , the Flag
will always be true
:
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
I extends any[] = [],
Flag extends boolean = I['length'] extends Start ? true : false
>
Since recursion will continuously generate complete answers, we define T
true
Flag
, that is, only the first item is processed each time. Use the replacement value N
, otherwise take the original first character:
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
I extends any[] = [],
Flag extends boolean = I['length'] extends Start ? true : false
> = I['length'] extends End
? T
: T extends [infer F, ...infer R]
? Flag extends false
? [F, ...Fill<R, N, Start, End, [...I, 0]>]
: [N, ...Fill<R, N, Start, End, [...I, 0]>]
: T
但这个答案没有通过测试, Flag
I
Start
,为了让超过后维持true
, when Flag
is true
, it can be passed in to overwrite the subsequent value:
// 本题答案
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
I extends any[] = [],
Flag extends boolean = I['length'] extends Start ? true : false
> = I['length'] extends End
? T
: T extends [infer F, ...infer R]
? Flag extends false
? [F, ...Fill<R, N, Start, End, [...I, 0]>]
: [N, ...Fill<R, N, Start, End, [...I, 0], Flag>]
: T
Summarize
The discussion address is: Intensive Reading "Flip, Fibonacci, AllCombinations..." Issue #432 dt-fe/weekly
If you'd like to join the discussion, click here , there are new topics every week, with a weekend or Monday release. Front-end intensive reading - help you filter reliable content.
Follow Front-end Intensive Reading WeChat Official Account
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Free to reprint - non-commercial - non-derivative - keep attribution ( Creative Commons 3.0 license )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。