小镇做题家 - TypeScript 类型大挑战(中等篇 - 下)

前端开发
2022年09月25日
1298

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 type T 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 as btn__price, modifier that changes the style of the block would be represented as btn--big or btn__price--warning.

Implement BEM<B, E, M> which generate string union from these three parameters. Where B is a string literal, E and M 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.valvalright.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 from S 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> like T > 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>>, ]

这里的解题思路是:

  1. 先过滤相等项;

    typescript
    type 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>
  2. 然后递归地把第一项做 “减一” 操作,直到它和第二项相等,或者变为 0 为止。

    typescript
    type 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 be Tuple

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 type T and returns whether T 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, the T must be a tuple, and the N must be an integer >=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 which T and N are required parameters, and Start and End are optional parameters.

The requirements for these parameters are: T must be a tuple, N can be any type of value, Start and End 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 Array T, any U and returns the index of the last U in Array T

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