小镇做题家 - TypeScript 类型大挑战(中等篇 - 下)
Medium 组(下)
Reverse
Implement the type version of
Array.reverse
typescript
type Reverse<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Reverse<[]>, []>>,
Expect<Equal<Reverse<['a', 'b']>, ['b', 'a']>>,
Expect<Equal<Reverse<['a', 'b', 'c']>, ['c', 'b', 'a']>>,
]
反转数组,这个也很简单:
typescript
type Reverse<T> = T extends [...infer R, infer L]
? [L, ...Reverse<R>]
: T
Flip Arguments
Implement the type version of lodash’s
_.flip
.Type
FlipArguments<T>
requires function typeT
and returns a new function type which has the same return type of T but reversed parameters.
typescript
type FlipArguments<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<FlipArguments<() => boolean>, () => boolean>>,
Expect<Equal<FlipArguments<(foo: string) => number>, (foo: string) => number>>,
Expect<Equal<FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>, (arg0: boolean, arg1: number, arg2: string) => void>>,
]
type errors = [
// @ts-expect-error
FlipArguments<'string'>,
// @ts-expect-error
FlipArguments<{ key: 'value' }>,
// @ts-expect-error
FlipArguments<['apple', 'banana', 100, { a: 1 }]>,
// @ts-expect-error
FlipArguments<null | undefined>,
]
首先需要对泛型加上约束解决 error cases:
typescript
type FlipArguments<T extends (...args: any[]) => any> = any
反转参数,这就和反转数组是一致的了:
typescript
type Reverse<T> = T extends [...infer R, infer L]
? [L, ...Reverse<R>]
: T
type FlipArguments<T extends (...args: any[]) => any> = T extends (...args: infer Args) => infer R
? (...args: Reverse<Args>) => R
: never
FlattenDepth
Recursively flatten array up to depth times.
typescript
type FlattenDepth = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<FlattenDepth<[]>, []>>,
Expect<Equal<FlattenDepth<[1, 2, 3, 4]>, [1, 2, 3, 4]>>,
Expect<Equal<FlattenDepth<[1, [2]]>, [1, 2]>>,
Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2>, [1, 2, 3, 4, [5]]>>,
Expect<Equal<FlattenDepth<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, [[5]]]>>,
Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 3>, [1, 2, 3, 4, [5]]>>,
Expect<Equal<FlattenDepth<[1, [2, [3, [4, [5]]]]], 19260817>, [1, 2, 3, 4, 5]>>,
]
这题给的初始内容有点少得可怜,所以泛型这一块就需要我们自己补充上去了:
typescript
type FlattenDepth<T extends unknown[], D extends number = 1> = any
需要注意的是这里的 case 有一个比较大的层级数,所以我们不能够采用减法的形式来计算,再借助一个初始值做加法比对:
typescript
type FlattenDepth<T extends unknown[], D extends number = 1, S extends number = 0>
解题思路就比较简单了:
typescript
type Plus<T extends number, R extends 0[] = []> = R['length'] extends T
? [...R, 0]['length']
: Plus<T, [0, ...R]>
type FlattenDepth<T extends unknown[], D extends number = 1, S extends number = 0> = S extends D
? T
: T extends [infer F, ...infer R]
? [
...(F extends any[]
? FlattenDepth<F, D, Plus<S>>
: [F]
),
...FlattenDepth<R, D, S>
]
: T
BEM style string
The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.
For example, the block component would be represented as
btn
, element that depends upon the block would be represented asbtn__price
, modifier that changes the style of the block would be represented asbtn--big
orbtn__price--warning
.Implement
BEM<B, E, M>
which generate string union from these three parameters. WhereB
is a string literal,E
andM
are string arrays (can be empty).
typescript
type BEM<B extends string, E extends string[], M extends string[]> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<BEM<'btn', ['price'], []>, 'btn__price'>>,
Expect<Equal<BEM<'btn', ['price'], ['warning', 'success']>, 'btn__price--warning' | 'btn__price--success' >>,
Expect<Equal<BEM<'btn', [], ['small', 'medium', 'large']>, 'btn--small' | 'btn--medium' | 'btn--large' >>,
]
这题我们需要利用 T[number]
将数组转成 Union:
typescript
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${E[number] extends '' ? '' : `__${E[number]}`}${M[number] extends '' ? '' : `--${M[number]}`}`
InorderTraversal
Implement the type version of binary tree inorder traversal.
typescript
interface TreeNode {
val: number
left: TreeNode | null
right: TreeNode | null
}
type InorderTraversal<T extends TreeNode | null> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const tree1 = {
val: 1,
left: null,
right: {
val: 2,
left: {
val: 3,
left: null,
right: null,
},
right: null,
},
} as const
const tree2 = {
val: 1,
left: null,
right: null,
} as const
const tree3 = {
val: 1,
left: {
val: 2,
left: null,
right: null,
},
right: null,
} as const
const tree4 = {
val: 1,
left: null,
right: {
val: 2,
left: null,
right: null,
},
} as const
type cases = [
Expect<Equal<InorderTraversal<null>, []>>,
Expect<Equal<InorderTraversal<typeof tree1>, [1, 3, 2]>>,
Expect<Equal<InorderTraversal<typeof tree2>, [1]>>,
Expect<Equal<InorderTraversal<typeof tree3>, [2, 1]>>,
Expect<Equal<InorderTraversal<typeof tree4>, [1, 2]>>,
]
这题我们需要注意的是它的取值顺序是 left.val
、val
、right.val
:
typescript
type InorderTraversal<T extends TreeNode | null> = T extends TreeNode
? [
...T['left'] extends TreeNode ? InorderTraversal<T['left']> : [],
T['val'],
...T['right'] extends TreeNode ? InorderTraversal<T['right']> : []
]
: []
Flip
Implement the type of
just-flip-object
.
typescript
type Flip<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect, NotEqual } from '@type-challenges/utils'
type cases = [
Expect<Equal<{ a: 'pi' }, Flip<{ pi: 'a' }>>>,
Expect<NotEqual<{ b: 'pi' }, Flip<{ pi: 'a' }>>>,
Expect<Equal<{ 3.14: 'pi'; true: 'bool' }, Flip<{ pi: 3.14; bool: true }>>>,
Expect<Equal<{ val2: 'prop2'; val: 'prop' }, Flip<{ prop: 'val'; prop2: 'val2' }>>>,
]
这题是把键值交换,需要注意的是,对象的键也就是 PropertyKey
他只能是 string | number | symbol
,如果不符合 PropertyKey
的约束,我们需要把它转成字符串:
typescript
type Flip<T extends Record<PropertyKey, any>> = {
[P in keyof T as T[P] extends PropertyKey ? T[P] : `${T[P]}`]: P
}
Fibonacci Sequence
Implement a generic Fibonacci<T> takes an number T and returns it’s corresponding Fibonacci number.
typescript
type Fibonacci<T extends number> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Fibonacci<3>, 2>>,
Expect<Equal<Fibonacci<8>, 21>>,
]
我们需要了解什么是斐波那契数列:1, 1, 2, 3, 5, 8, 13, ...
然后从中取出指定索引的值,所以我们需要一些辅助类型来存储上一个值和当前值,并且让计算从第 2 项才开始:
typescript
type NumberToTuple<T extends Number, R extends 0[] = []> = R['length'] extends T
? R
: NumberToTuple<T, [...R, 0]>
type Plus<T extends number, N extends number> = [
...NumberToTuple<T>,
...NumberToTuple<N>
]['length'] & number
// 1, 1, 2, 3, 5, 8, ...
type Fibonacci<
T extends number,
Start extends number = 2,
Prev extends number = 0,
Last extends number = 1
> = T extends 0 | 1
? 1
: Start extends T
? Plus<Prev, Last>
: Fibonacci<T, Plus<Start, 1>, Last, Plus<Prev, Last>>
AllCombinations
Implement type
AllCombinations<S>
that return all combinations of strings which use characters fromS
at most once.
typescript
type AllCombinations<S> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<AllCombinations<''>, ''>>,
Expect<Equal<AllCombinations<'A'>, '' | 'A'>>,
Expect<Equal<AllCombinations<'AB'>, '' | 'A' | 'B' | 'AB' | 'BA'>>,
Expect<Equal<AllCombinations<'ABC'>, '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'>>,
Expect<Equal<AllCombinations<'ABCD'>, '' | 'A' | 'B' | 'C' | 'D' | 'AB' | 'AC' | 'AD' | 'BA' | 'BC' | 'BD' | 'CA' | 'CB' | 'CD' | 'DA' | 'DB' | 'DC' | 'ABC' | 'ABD' | 'ACB' | 'ACD' | 'ADB' | 'ADC' | 'BAC' | 'BAD' | 'BCA' | 'BCD' | 'BDA' | 'BDC' | 'CAB' | 'CAD' | 'CBA' | 'CBD' | 'CDA' | 'CDB' | 'DAB' | 'DAC' | 'DBA' | 'DBC' | 'DCA' | 'DCB' | 'ABCD' | 'ABDC' | 'ACBD' | 'ACDB' | 'ADBC' | 'ADCB' | 'BACD' | 'BADC' | 'BCAD' | 'BCDA' | 'BDAC' | 'BDCA' | 'CABD' | 'CADB' | 'CBAD' | 'CBDA' | 'CDAB' | 'CDBA' | 'DABC' | 'DACB' | 'DBAC' | 'DBCA' | 'DCAB' | 'DCBA'>>,
]
首先要做的就是把字符串给转成联合类型:
typescript
type StringToUnion<S extends string = ''> = S extends `${infer F}${infer R}`
? F | StringToUnion<R>
: never
然后利用分布式条件类型自动生成结果:
typescript
type StringToUnion<S extends string = ''> = S extends `${infer F}${infer R}`
? F | StringToUnion<R>
: never
type Combinations<T extends string> = [T] extends [never]
? ''
: '' | { [K in T]: `${K}${Combinations<Exclude<T, K>>}` }[T]
type AllCombinations<S extends string> = Combinations<StringToUnion<S>>
分析结果可以看这里:https://www.yuque.com/liaojie3/yuiou5/ogyeiz#giVmO
Greater Than
In This Challenge, You should implement a type
GreaterThan<T, U>
likeT > U
Negative numbers do not need to be considered.
typescript
type GreaterThan<T extends number, U extends number> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<GreaterThan<1, 0>, true>>,
Expect<Equal<GreaterThan<5, 4>, true>>,
Expect<Equal<GreaterThan<4, 5>, false>>,
Expect<Equal<GreaterThan<0, 0>, false>>,
Expect<Equal<GreaterThan<20, 20>, false>>,
Expect<Equal<GreaterThan<10, 100>, false>>,
Expect<Equal<GreaterThan<111, 11>, true>>,
]
这里的解题思路是:
-
先过滤相等项;
typescripttype GT<T extends number, U extends number> = any type GreaterThan<T extends number, U extends number> = Equal<T, U> extends true ? false : GT<T, U>
-
然后递归地把第一项做 “减一” 操作,直到它和第二项相等,或者变为 0 为止。
typescripttype NumberToTuple<T extends number, Res extends 0[] = []> = Res['length'] extends T ? Res : NumberToTuple<T, [...Res, 0]> type MinusOne<T extends number, Res extends 0[] = NumberToTuple<T>> = Res extends [infer F, ...infer R] ? R['length'] : never type GT<T extends number, U extends number> = T extends U ? true : T extends 0 ? false : GT<MinusOne<T>, U> type GreaterThan<T extends number, U extends number> = Equal<T, U> extends true ? false : GT<T, U>
Zip
In This Challenge, You should implement a type
Zip<T, U>
, T and U must beTuple
typescript
type Zip = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Zip<[], []>, []>>,
Expect<Equal<Zip<[1, 2], [true, false]>, [[1, true], [2, false]]>>,
Expect<Equal<Zip<[1, 2, 3], ['1', '2']>, [[1, '1'], [2, '2']]>>,
Expect<Equal<Zip<[], [1, 2, 3]>, []>>,
Expect<Equal<Zip<[[1, 2]], [3]>, [[[1, 2], 3]]>>,
]
根据题目要求,它需要两个泛型参数,并且都得是一个数组:
typescript
type Zip<T extends unknown[], U extends unknown[]> = any
之后就是简单的数组操作了:
typescript
type Zip<T extends unknown[], U extends unknown[]> = T extends []
? []
: T extends [infer TF, ...infer TR]
? U extends [infer UF, ...infer UR]
? [[TF, UF], ...Zip<TR, UR>]
: []
: []
IsTuple
Implement a type
IsTuple
, which takes an input typeT
and returns whetherT
is tuple type.
typescript
type IsTuple<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<IsTuple<[]>, true>>,
Expect<Equal<IsTuple<[number]>, true>>,
Expect<Equal<IsTuple<readonly [1]>, true>>,
Expect<Equal<IsTuple<{ length: 1 }>, false>>,
Expect<Equal<IsTuple<number[]>, false>>,
Expect<Equal<IsTuple<never>, false>>,
]
这题是比较简单的,我们可以使用 [any?]
来表示一个数组。当然,我们需要先排除掉 never:
typescript
type IsTuple<T> = [T] extends [never]
? false
: T extends readonly [any?]
? true
: false
Chunk
Do you know
lodash
?Chunk
is a very useful function in it, now let’s implement it.
Chunk<T, N>
accepts two required type parameters, theT
must be atuple
, and theN
must be aninteger >=1
typescript
type Chunk = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Chunk<[], 1>, []>>,
Expect<Equal<Chunk<[1, 2, 3], 1>, [[1], [2], [3]]>>,
Expect<Equal<Chunk<[1, 2, 3], 2>, [[1, 2], [3]]>>,
Expect<Equal<Chunk<[1, 2, 3, 4], 2>, [[1, 2], [3, 4]]>>,
Expect<Equal<Chunk<[1, 2, 3, 4], 5>, [[1, 2, 3, 4]]>>,
Expect<Equal<Chunk<[1, true, 2, false], 2>, [[1, true], [2, false]]>>,
]
首先,根据题目要求把泛型以及约束加上:
typescript
type Chunk<T extends unknown[], N extends number = 1> = any
先实现一个用于取值的 Slice 类型,它会从指定的数组中取出指定长度的项:
typescript
type GetItem<T extends unknown[], N extends number = 1, Res extends unknown[] = []> = Res['length'] extends N
? Res
: T extends [infer F, ...infer R]
? GetItem<R, N, [...Res, F]>
: Res
然后就是常规的数组操作了:
typescript
type Slice<T extends unknown[], N extends number = 1, Res extends unknown[] = []> = Res['length'] extends N
? Res
: T extends [infer F, ...infer R]
? Slice<R, N, [...Res, F]>
: Res
type Chunk<T extends unknown[], N extends number = 1, Res extends unknown[] = []> = T extends []
? []
: T extends [...Slice<T, N>, ...infer R]
? R extends []
? [...Res, Slice<T, N>]
: Chunk<R, N, [...Res, Slice<T, N>]>
: never
Fill
Fill
, a common JavaScript function, now let us implement it with types.
Fill<T, N, Start?, End?>
, as you can see,Fill
accepts four types of parameters, of whichT
andN
are required parameters, andStart
andEnd
are optional parameters.The requirements for these parameters are:
T
must be atuple
,N
can be any type of value,Start
andEnd
must be integers greater than or equal to 0.
typescript
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Fill<[], 0>, []>>,
Expect<Equal<Fill<[], 0, 0, 3>, []>>,
Expect<Equal<Fill<[1, 2, 3], 0, 0, 0>, [1, 2, 3]>>,
Expect<Equal<Fill<[1, 2, 3], 0, 2, 2>, [1, 2, 3]>>,
Expect<Equal<Fill<[1, 2, 3], 0>, [0, 0, 0]>>,
Expect<Equal<Fill<[1, 2, 3], true>, [true, true, true]>>,
Expect<Equal<Fill<[1, 2, 3], true, 0, 1>, [true, 2, 3]>>,
Expect<Equal<Fill<[1, 2, 3], true, 1, 3>, [1, true, true]>>,
Expect<Equal<Fill<[1, 2, 3], true, 10, 0>, [1, 2, 3]>>,
Expect<Equal<Fill<[1, 2, 3], true, 0, 10>, [true, true, true]>>,
]
我们先排除 Start 和 End 相同,或者 End 为 0 的 case,然后再处理其它的:
typescript
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length']
> = Start extends End ? T : End extends 0 ? T : FillRest<T, N, Start, End>
FillReset
利用一个计数器 Count 来决定什么时候开始对值进行替换:
typescript
type PlusOne<T extends number, Res extends 0[] = []> = Res['length'] extends T
? [...Res, 0]['length']
: PlusOne<T, [...Res, 0]>
type FillRest<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
Count extends number = 0,
Res extends unknown[] = []
> = Start extends End
? [...Res, ...T]
: T extends [infer F, ... infer R]
? Count extends Start
? FillRest<R, N, PlusOne<Start>, End, PlusOne<Count>, [...Res, N]>
: FillRest<R, N, Start, End, PlusOne<Count>, [...Res, F]>
: Res
Trim Right
Implement
TrimRight<T>
which takes an exact string type and returns a new string with the whitespace ending removed.
typescript
type TrimRight<S extends string> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<TrimRight<'str'>, 'str'>>,
Expect<Equal<TrimRight<'str '>, 'str'>>,
Expect<Equal<TrimRight<'str '>, 'str'>>,
Expect<Equal<TrimRight<' str '>, ' str'>>,
Expect<Equal<TrimRight<' foo bar \n\t '>, ' foo bar'>>,
Expect<Equal<TrimRight<''>, ''>>,
Expect<Equal<TrimRight<'\n\t '>, ''>>,
]
这和之前的 TrimLeft 一样的处理方式:
typescript
type TrimRight<S extends string> = S extends `${infer R}${' ' | '\n' | '\t'}`
? TrimRight<R>
: S
Without
Implement the type version of Lodash.without, Without<T, U> takes an Array T, number or array U and returns an Array without the elements of U.
typescript
type Without<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Without<[1, 2], 1>, [2]>>,
Expect<Equal<Without<[1, 2, 4, 1, 5], [1, 2]>, [4, 5]>>,
Expect<Equal<Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>, []>>,
]
就是过滤操作,首先我们把 U 转成联合类型:
typescript
type ToUnion<T extends number | unknown[]> = T extends number
? T
: T extends any[]
? T[number]
: never
然后就是常规的对比操作了:
typescript
type ToUnion<T extends number | unknown[]> = T extends number
? T
: T extends any[]
? T[number]
: never
type Without<
T,
U extends number | unknown[],
D = ToUnion<U>,
Result extends unknown[] = []
> = T extends [infer F, ...infer R]
? F extends D
? Without<R, U, D, Result>
: Without<R, U, D, [...Result, F]>
: Result
Trunc
Implement the type version of
Math.trunc
, which takes string or number and returns the integer part of a number by removing any fractional digits.
typescript
type Trunc = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Trunc<0.1>, '0'>>,
Expect<Equal<Trunc<1.234>, '1'>>,
Expect<Equal<Trunc<12.345>, '12'>>,
Expect<Equal<Trunc<-5.1>, '-5'>>,
Expect<Equal<Trunc<'1.234'>, '1'>>,
Expect<Equal<Trunc<'-10.234'>, '-10'>>,
Expect<Equal<Trunc<10>, '10'>>,
]
移除小数位,直接字符串操作即可:
typescript
type Trunc<T extends number | string> = `${T}` extends `${infer F}.${infer R}`
? `${F}`
: `${T}`
IndexOf
Implement the type version of Array.indexOf, indexOf<T, U> takes an Array T, any U and returns the index of the first U in Array T.
typescript
type IndexOf<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<IndexOf<[1, 2, 3], 2>, 1>>,
Expect<Equal<IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 2>>,
Expect<Equal<IndexOf<[0, 0, 0], 2>, -1>>,
Expect<Equal<IndexOf<[string, 1, number, 'a'], number>, 2>>,
Expect<Equal<IndexOf<[string, 1, number, 'a', any], any>, 4>>,
]
递归比对即可:
typescript
type PlusOne<T extends number, Res extends 0[] = []> = Res['length'] extends T
? [...Res, 0]['length']
: PlusOne<T, [...Res, 0]>
type IndexOf<T, U, I extends number = 0> = T extends [infer F, ...infer R]
? Equal<F, U> extends true
? I
: IndexOf<R, U, PlusOne<I>>
: -1
Join
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.
typescript
type Join<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Join<['a', 'p', 'p', 'l', 'e'], '-'>, 'a-p-p-l-e'>>,
Expect<Equal<Join<['Hello', 'World'], ' '>, 'Hello World'>>,
Expect<Equal<Join<['2', '2', '2'], 1>, '21212'>>,
Expect<Equal<Join<['o'], 'u'>, 'o'>>,
]
我能想到的是先将 T 中所有项都和 U 拼接起来,然后再去除最后一个 U 便可以解开这道题:
typescript
type RemoveLast<T extends string, U extends string> = T extends `${infer R}${U}`
? R
: T
type FullJoin<T extends string[], U extends string, Result extends string = ''> = T extends [infer F extends string, ...infer R extends string[]]
? FullJoin<R, U, `${Result}${F}${U}`>
: Result
type Join<T extends string[], U extends string | number> = RemoveLast<FullJoin<T, `${U}`>, `${U}`>
LastIndexOf
Implement the type version of
Array.lastIndexOf
,LastIndexOf<T, U>
takes an ArrayT
, anyU
and returns the index of the lastU
in ArrayT
typescript
type LastIndexOf<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>,
Expect<Equal<LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>,
Expect<Equal<LastIndexOf<[0, 0, 0], 2>, -1>>,
Expect<Equal<LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>,
Expect<Equal<LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>,
]
这题相对于 IndexOf 会更加简单,每一次取数组中最后一位作比对操作,如果相同,则数组中剩余项的个数即为 LastIndex:
typescript
type LastIndexOf<T extends unknown[], U> = T extends [...infer R, infer L]
? Equal<U, L> extends true
? R['length']
: LastIndexOf<R, U>
: -1
Unique
Implement the type version of Lodash.uniq, Unique
takes an Array T, returns the Array T without repeated values.
typescript
type Unique<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Unique<[1, 1, 2, 2, 3, 3]>, [1, 2, 3]>>,
Expect<Equal<Unique<[1, 2, 3, 4, 4, 5, 6, 7]>, [1, 2, 3, 4, 5, 6, 7]>>,
Expect<Equal<Unique<[1, 'a', 2, 'b', 2, 'a']>, [1, 'a', 2, 'b']>>,
Expect<Equal<Unique<[string, number, 1, 'a', 1, string, 2, 'b', 2, number]>, [string, number, 1, 'a', 2, 'b']>>,
Expect<Equal<Unique<[unknown, unknown, any, any, never, never]>, [unknown, any, never]>>,
]
通过 Includes 来判断是否已经取过该值即可:
typescript
type Includes<T extends unknown[], U> = T extends [infer F, ...infer R]
? Equal<U, F> extends true
? true
: Includes<R, U>
: false
type Unique<T extends unknown[], Res extends unknown[] = []> = T extends [infer F, ...infer R]
? Includes<Res, F> extends true
? Unique<R, Res>
: Unique<R, [...Res, F]>
: Res
MapTypes
Implement
MapTypes<T, R>
which will transform types in object T to different types defined by type R which has the following structure
typescript
type MapTypes<T, R> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<MapTypes<{ stringToArray: string }, { mapFrom: string; mapTo: [] }>, { stringToArray: [] }>>,
Expect<Equal<MapTypes<{ stringToNumber: string }, { mapFrom: string; mapTo: number }>, { stringToNumber: number }>>,
Expect<Equal<MapTypes<{ stringToNumber: string; skipParsingMe: boolean }, { mapFrom: string; mapTo: number }>, { stringToNumber: number; skipParsingMe: boolean }>>,
Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date } | { mapFrom: string; mapTo: null }>, { date: null | Date }>>,
Expect<Equal<MapTypes<{ date: string }, { mapFrom: string; mapTo: Date | null }>, { date: null | Date }>>,
Expect<Equal<MapTypes<{ fields: Record<string, boolean> }, { mapFrom: Record<string, boolean>; mapTo: string[] }>, { fields: string[] }>>,
Expect<Equal<MapTypes<{ name: string }, { mapFrom: boolean; mapTo: never }>, { name: string }>>,
Expect<Equal<MapTypes<{ name: string; date: Date }, { mapFrom: string; mapTo: boolean } | { mapFrom: Date; mapTo: string }>, { name: boolean; date: string }>>,
]
这题需要注意的是 U 有可能是一个联合类型,所以需要进一步判断:
typescript
type Includes<T extends Record<'mapFrom' | 'mapTo', any>, U> = T extends {}
? T['mapFrom'] extends U
? T['mapTo']
: never
: T['mapTo']
type MapTypes<T, R extends Record<'mapFrom' | 'mapTo', any>> = {
[P in keyof T]: T[P] extends R['mapFrom']
? Includes<R, T[P]>
: T[P]
}
Construct Tuple
Construct a tuple with a given length.
typescript
type ConstructTuple<L extends number> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<ConstructTuple<0>, []>>,
Expect<Equal<ConstructTuple<2>, [unknown, unknown]>>,
Expect<Equal<ConstructTuple<999>['length'], 999>>,
// @ts-expect-error
Expect<Equal<ConstructTuple<1000>['length'], 1000>>,
]
生成一个指定长度的数组,这点在之前的挑战中已经多次实现过了,最后一个 case 是因为递归上限会导致出错:
typescript
type ConstructTuple<L extends number, Res extends unknown[] = []> = Res['length'] extends L
? Res
: ConstructTuple<L, [...Res, unknown]>
Number Range
Sometimes we want to limit the range of numbers…
typescript
import type { Equal, Expect } from '@type-challenges/utils'
type Result1 = | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Result2 = | 0 | 1 | 2
type Result3 =
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
| 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20
| 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30
| 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40
| 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50
| 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60
| 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70
| 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80
| 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90
| 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100
| 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110
| 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120
| 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130
| 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140
type cases = [
Expect<Equal<NumberRange<2, 9>, Result1>>,
Expect<Equal<NumberRange<0, 2>, Result2>>,
Expect<Equal<NumberRange<0, 140>, Result3>>,
]
加一递归即可:
typescript
type PlusOne<T extends Number, Res extends 0[] = []> = Res['length'] extends T
? [...Res, 0]['length']
: PlusOne<T, [...Res, 0]>
type NumberRange<L extends number, H extends number, Res = H> = L extends H
? Res
: NumberRange<PlusOne<L>, H, L | Res>
Combination
Given an array of strings, do Permutation & Combination.
It’s also useful for the prop types like video controlsList
typescript
type Combination<T extends string[]> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Combination<['foo', 'bar', 'baz']>,
'foo' | 'bar' | 'baz' | 'foo bar' | 'foo bar baz' | 'foo baz' | 'foo baz bar' | 'bar foo' | 'bar foo baz' | 'bar baz' | 'bar baz foo' | 'baz foo' | 'baz foo bar' | 'baz bar' | 'baz bar foo'>>,
]
这题类似于 AllCombinations,但比那题要简单一点:
typescript
type Combination<T extends string[], U = T[number], D = U> = D extends string
? `${D}` | `${D} ${Combination<[], Exclude<U, D>>}`
: never
Subsequence
Given an array of unique elements, return all possible subsequences.
A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements.
typescript
type Subsequence<T extends any[]> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Subsequence<[1, 2]>, [] | [1] | [2] | [1, 2]>>,
Expect<Equal<Subsequence<[1, 2, 3]>, [] | [1] | [2] | [1, 2] | [3] | [1, 3] | [2, 3] | [1, 2, 3] >>,
]
数组操作:
typescript
type Subsequence<T extends any[], Result extends any[] = []> = T extends [infer F, ...infer R]
? Result | Subsequence<R, [...Result, F]> | Subsequence<R, Result>
: Result
以 [1, 2]
为例:
typescript
// 1.
type Subsequence<[1, 2], []> = [1, 2] extends [1, ...[2]]
? [] | Subsequence<[2], [...[], 1]> | Subsequence<[2], []>
: []
// 2.1
type Subsequence<[2], [1]> = [2] extends [2, ...[]]
? [] | Subsequence<[], [...[1], 2]> | Subsequence<[], [1]>
: [1]
// => [] | [1, 2] | [1]
// 2.2
type Subsequence<[2], []> = [2] extends [2, ...[]]
? [] | Subsequence<[], [...[], 2]> | Subsequence<[], []>
: []
// => [2] | []
// 3. 最终返回
= [1, 2] | [] | [2] | [1]
Trim
Implement
Trim<T>
which takes an exact string type and returns a new string with the whitespace from both ends removed.
typescript
type Trim<S extends string> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Trim<'str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<' str'>, 'str'>>,
Expect<Equal<Trim<'str '>, 'str'>>,
Expect<Equal<Trim<' str '>, 'str'>>,
Expect<Equal<Trim<' \n\t foo bar \t'>, 'foo bar'>>,
Expect<Equal<Trim<''>, ''>>,
Expect<Equal<Trim<' \n\t '>, ''>>,
]
字符串操作:
typescript
type Removable = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${Removable}${infer R}`
? R extends `${infer L}${Removable}`
? Trim<L>
: Trim<R>
: S extends `${infer L}${Removable}`
? Trim<L>
: S