小镇做题家 - TypeScript 类型大挑战(中等篇 - 中)
Medium 组(中)
KebabCase
Replace the
camelCase
orPascalCase
string withkebab-case
.
typescript
type KebabCase<S> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<KebabCase<'FooBarBaz'>, 'foo-bar-baz'>>,
Expect<Equal<KebabCase<'fooBarBaz'>, 'foo-bar-baz'>>,
Expect<Equal<KebabCase<'foo-bar'>, 'foo-bar'>>,
Expect<Equal<KebabCase<'foo_bar'>, 'foo_bar'>>,
Expect<Equal<KebabCase<'Foo-Bar'>, 'foo--bar'>>,
Expect<Equal<KebabCase<'ABC'>, 'a-b-c'>>,
Expect<Equal<KebabCase<'-'>, '-'>>,
Expect<Equal<KebabCase<''>, ''>>
]
在 TypeScript 的工具类中,有一个工具是可以把首字母转成小写,它就是 Uncapitalize
:
typescript
type A = 'HELLO WORLD'
type E = Expect<Equal<Uncapitalize<A>, 'hELLO WORLD'>>
因为这一题里面首字母是大写时,只需要把它转成小写即可,而不需要再加上 -
,所以我们需要对剩余字符进行判断:
typescript
type KebabCase<S> = S extends `${infer F}${infer R}`
? R extends Uncapitalize<R>
? `${Lowercase<F>}${KebabCase<R>}`
: `${Lowercase<F>}-${KebabCase<R>}`
: S
Diff
Get an
Object
that is the difference betweenO
&O1
typescript
type Diff<O, O1> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type Foo = {
name: string
age: string
}
type Bar = {
name: string
age: string
gender: number
}
type Coo = {
name: string
gender: number
}
type cases = [
Expect<Equal<Diff<Foo, Bar>, { gender: number }>>,
Expect<Equal<Diff<Bar, Foo>, { gender: number }>>,
Expect<Equal<Diff<Foo, Coo>, { age: string; gender: number }>>,
Expect<Equal<Diff<Coo, Foo>, { age: string; gender: number }>>,
]
取差集,基操,只需要去除交集即可:
typescript
type Diff<O, O1> = {
[P in Exclude<keyof O, keyof O1> | Exclude<keyof O1, keyof O>]: P extends keyof O
? O[P]
: P extends keyof O1
? O1[P]
: never
}
AnyOf
Implement Python liked
any
function in the type system. A type takes the Array and returnstrue
if any element of the Array is true. If the Array is empty, returnfalse
.
typescript
type AnyOf<T extends readonly any[]> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<AnyOf<[1, 'test', true, [1], { name: 'test' }, { 1: 'test' }]>, true>>,
Expect<Equal<AnyOf<[1, '', false, [], {}]>, true>>,
Expect<Equal<AnyOf<[0, 'test', false, [], {}]>, true>>,
Expect<Equal<AnyOf<[0, '', true, [], {}]>, true>>,
Expect<Equal<AnyOf<[0, '', false, [1], {}]>, true>>,
Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }]>, true>>,
Expect<Equal<AnyOf<[0, '', false, [], { 1: 'test' }]>, true>>,
Expect<Equal<AnyOf<[0, '', false, [], { name: 'test' }, { 1: 'test' }]>, true>>,
Expect<Equal<AnyOf<[0, '', false, [], {}]>, false>>,
Expect<Equal<AnyOf<[]>, false>>,
]
只要传入的数组中有一项为 true,则结果为 true。首先我们需要知道哪些值是 falsy 值,从 cases 中可以得知:
typescript
type Falsy = 0 | '' | [] | false | Record<PropertyKey, never>
需要注意的是,空对象类型得采用 Record<PropertyKey, never>
来表示。如此一来,解题也变得非常简单了:
typescript
type Falsy = 0 | '' | [] | false | Record<PropertyKey, never>
type AnyOf<T extends readonly any[]> = T[number] extends Falsy
? false
: true
IsNever
Implement a type IsNever, which takes input type
T
.If the type of resolves to
never
, returntrue
, otherwisefalse
.
typescript
type IsNever<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<IsNever<never>, true>>,
Expect<Equal<IsNever<never | string>, false>>,
Expect<Equal<IsNever<''>, false>>,
Expect<Equal<IsNever<undefined>, false>>,
Expect<Equal<IsNever<null>, false>>,
Expect<Equal<IsNever<[]>, false>>,
Expect<Equal<IsNever<{}>, false>>,
]
我总感觉这题不应该出现在这个栏目:
typescript
type IsNever<T> = [T] extends [never] ? true : false
IsUnion
Implement a type
IsUnion
, which takes an input typeT
and returns whetherT
resolves to a union type.
typescript
type IsUnion<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<IsUnion<string>, false>>,
Expect<Equal<IsUnion<string | number>, true>>,
Expect<Equal<IsUnion<'a' | 'b' | 'c' | 'd'>, true>>,
Expect<Equal<IsUnion<undefined | null | void | ''>, true>>,
Expect<Equal<IsUnion<{ a: string } | { a: number }>, true>>,
Expect<Equal<IsUnion<{ a: string | number }>, false>>,
Expect<Equal<IsUnion<[string | number]>, false>>,
// Cases where T resolves to a non-union type.
Expect<Equal<IsUnion<string | never>, false>>,
Expect<Equal<IsUnion<string | unknown>, false>>,
Expect<Equal<IsUnion<string | any>, false>>,
Expect<Equal<IsUnion<string | 'a'>, false>>,
Expect<Equal<IsUnion<never>, false>>,
]
首先,我们先要把 never 排除掉:
typescript
type IsUnion<T, A = T> = [T] extends [never]
? false
: // ...
如果传入的是联合类型,那么就拿第一项和整个联合类型进行对比:
typescript
type IsUnion<T, A = T> = [T] extends [never]
? false
: T extends A
? // ...
: // ...
如果 T 是一个非联合类型,那么 T extends A
这条语句永远都会是 true,所以我们还需要进一步判断,通过一个小技巧:
typescript
type IsUnion<T, A = T> = [T] extends [never]
? false
: T extends A
? Equal<[T], [A]> extends true
? false
: true
: false
如果 T 是一个联合类型,假设是:string | number
,那么 [T]
就是 [string]
,而 [A]
就是 [string | number]
,它们两者必不相等;如果 T 是一个非联合类型,假设是:string
,那么 [T]
和 [A]
都是 [string]
,两者相等。所以以此来判断它是否为一个联合类型。
ReplaceKeys
Implement a type ReplaceKeys, that replace keys in union types, if some type has not this key, just skip replacing,
A type takes three arguments.
typescript
type ReplaceKeys<U, T, Y> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type NodeA = {
type: 'A'
name: string
flag: number
}
type NodeB = {
type: 'B'
id: number
flag: number
}
type NodeC = {
type: 'C'
name: string
flag: number
}
type ReplacedNodeA = {
type: 'A'
name: number
flag: string
}
type ReplacedNodeB = {
type: 'B'
id: number
flag: string
}
type ReplacedNodeC = {
type: 'C'
name: number
flag: string
}
type NoNameNodeA = {
type: 'A'
flag: number
name: never
}
type NoNameNodeC = {
type: 'C'
flag: number
name: never
}
type Nodes = NodeA | NodeB | NodeC
type ReplacedNodes = ReplacedNodeA | ReplacedNodeB | ReplacedNodeC
type NodesNoName = NoNameNodeA | NoNameNodeC | NodeB
type cases = [
Expect<Equal<ReplaceKeys<Nodes, 'name' | 'flag', { name: number; flag: string }>, ReplacedNodes>>,
Expect<Equal<ReplaceKeys<Nodes, 'name', { aa: number }>, NodesNoName>>,
]
题目中指出需要三个泛型参数,分别是:源类型(U)、需要的键(T)以及替换键组成的接口(Y),而我们要做的是,把 U 中所有符合 T 的键都替换成 Y 里面的类型。
所以解题的方法也很简单了,把需要匹配的条件都列出来即可:
typescript
type ReplaceKeys<U, T, Y> = {
[P in keyof U]: P extends T
? P extends keyof Y
? Y[P]
: never
: U[P]
}
Remove Index Signature
Implement
RemoveIndexSignature<T>
, exclude the index signature from object types.
typescript
type RemoveIndexSignature<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type Foo = {
[key: string]: any
foo(): void
}
type Bar = {
[key: number]: any
bar(): void
0: string
}
const foobar = Symbol('foobar')
type FooBar = {
[key: symbol]: any
[foobar](): void
}
type Baz = {
bar(): void
baz: string
}
type cases = [
Expect<Equal<RemoveIndexSignature<Foo>, { foo(): void }>>,
Expect<Equal<RemoveIndexSignature<Bar>, { bar(): void; 0: string }>>,
Expect<Equal<RemoveIndexSignature<FooBar>, { [foobar](): void }>>,
Expect<Equal<RemoveIndexSignature<Baz>, { bar(): void; baz: string }>>,
]
从对象中移除索引类型,那么我们先需要判断哪些才是索引类型:
typescript
type IsSignature<T> = string extends T
? true
: number extends T
? true
: symbol extends T
? true
: false
从对象中删除一个键,将该键置为 never 即可:
typescript
type Obj = {
a: string;
b: number;
}
type DeleteKeys<T> = {
[K in keyof T as never]: T[K]
}
type EmptyObj = DeleteKeys<Obj> // {}
所以最终答案如下代码所示:
typescript
type IsSignature<T> = string extends T
? true
: number extends T
? true
: symbol extends T
? true
: false
type RemoveIndexSignature<T> = {
[P in keyof T as IsSignature<P> extends true ? never : P]: T[P]
}
Percentage Parser
Implement PercentageParser
. According to the
/^(\+|\-)?(\d*)?(\%)?$/
regularity to match T and get three matches.
typescript
type PercentageParser<A extends string> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type Case0 = ['', '', '']
type Case1 = ['+', '', '']
type Case2 = ['+', '1', '']
type Case3 = ['+', '100', '']
type Case4 = ['+', '100', '%']
type Case5 = ['', '100', '%']
type Case6 = ['-', '100', '%']
type Case7 = ['-', '100', '']
type Case8 = ['-', '1', '']
type Case9 = ['', '', '%']
type Case10 = ['', '1', '']
type Case11 = ['', '100', '']
type cases = [
Expect<Equal<PercentageParser<''>, Case0>>,
Expect<Equal<PercentageParser<'+'>, Case1>>,
Expect<Equal<PercentageParser<'+1'>, Case2>>,
Expect<Equal<PercentageParser<'+100'>, Case3>>,
Expect<Equal<PercentageParser<'+100%'>, Case4>>,
Expect<Equal<PercentageParser<'100%'>, Case5>>,
Expect<Equal<PercentageParser<'-100%'>, Case6>>,
Expect<Equal<PercentageParser<'-100'>, Case7>>,
Expect<Equal<PercentageParser<'-1'>, Case8>>,
Expect<Equal<PercentageParser<'%'>, Case9>>,
Expect<Equal<PercentageParser<'1'>, Case10>>,
Expect<Equal<PercentageParser<'100'>, Case11>>,
]
这题可以分两步来判断,一个是以 '+' | '-'
开头,另一个是以 '%'
结束:
typescript
type PercentageParser<A extends string> = A extends `${infer S extends '+' | '-'}${infer R}`
? R extends `${infer F}%`
? [S, F, '%']
: [S, R, '']
: A extends `${infer F}%`
? ['', F, '%']
: ['', A, '']
Drop Char
Drop a specified char from a string.
typescript
type DropChar<S, C> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
// @ts-expect-error
Expect<Equal<DropChar<'butter fly!', ''>, 'butterfly!'>>,
Expect<Equal<DropChar<'butter fly!', ' '>, 'butterfly!'>>,
Expect<Equal<DropChar<'butter fly!', '!'>, 'butter fly'>>,
Expect<Equal<DropChar<' butter fly! ', ' '>, 'butterfly!'>>,
Expect<Equal<DropChar<' b u t t e r f l y ! ', ' '>, 'butterfly!'>>,
Expect<Equal<DropChar<' b u t t e r f l y ! ', 'b'>, ' u t t e r f l y ! '>>,
Expect<Equal<DropChar<' b u t t e r f l y ! ', 't'>, ' b u e r f l y ! '>>,
]
又是一道字符串操作题,递归处理即可:
typescript
type DropChar<S, C, Result extends string = ''> = S extends `${infer F}${infer R}`
? F extends C
? DropChar<R, C, Result>
: DropChar<R, C, `${Result}${F}`>
: Result
或者:
typescript
type DropChar<S extends string, C extends string> = S extends `${infer F}${infer R}`
? `${F extends C ? '' : F}${DropChar<R, C>}`
: S
这题的 // @ts-expect-error
有点谜,就不处理了。
MinusOne
Given a number (always positive) as a type. Your type should return the number decreased by one.
typescript
type MinusOne<T extends number> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<MinusOne<1>, 0>>,
Expect<Equal<MinusOne<55>, 54>>,
Expect<Equal<MinusOne<3>, 2>>,
Expect<Equal<MinusOne<100>, 99>>,
Expect<Equal<MinusOne<1101>, 1100>>,
]
数组它承受了它不该承受的东西,只因为它有个 length
,所以在做这种加减法的类型体操,我们只需要给个数组,最终拿到它的 Length 即可:
typescript
type Fill<N extends number, R extends number[] = []> = R['length'] extends N
? R
: Fill<N, [...R, 0]>
type MinusOne<T extends number, A extends number[] = []> = Fill<T> extends [infer F, ...infer R]
? R['length']
: never
但这一题,需要注意一下递归溢出问题,所以我们需要用其他办法来创建一个符合题目要求的数组:
typescript
type Dict = {
'0': [];
'1': [0];
'2': [0, 0];
'3': [0, 0, 0];
'4': [0, 0, 0, 0];
'5': [0, 0, 0, 0, 0];
'6': [0, 0, 0, 0, 0, 0];
'7': [0, 0, 0, 0, 0, 0, 0];
'8': [0, 0, 0, 0, 0, 0, 0, 0];
'9': [0, 0, 0, 0, 0, 0, 0, 0, 0];
}
type FillTenTimes<A extends 0[] = []> = [
...A, ...A, ...A, ...A, ...A,
...A, ...A, ...A, ...A, ...A
]
type Fill<N extends string, Result extends 0[] = []> = N extends `${infer F extends keyof Dict}${infer R}`
? Fill<R, [...FillTenTimes<Result>, ...Dict[F]]>
: Result
type MinusOne<T extends number> = Fill<`${T}`> extends [infer F, ...infer R]
? R['length']
: T
这里解释一下这个数组是怎么被创建出来的,以最后一个 case 的 1101 为例:
- 交给
Fill
来填充的是字符串1101
,此时F === '1'
,R === '101'
,初始的Result
为[]
,它经过FillTenTimes
处理后还是[]
,Dict[F]
为[0]
,所以交给第二次递归的是Fill<'101', [0]>
; - 第二次递归时:
F === '1'
,R === '01'
,Result === [0]
,然后 Result 经过FillTenTimes
处理后变成[10 * 0]
,Dict[F]
为[0]
,所以交给第三次递归的是Fill<'01', [10 * 0, 0]>
; - 第三次递归时:
F === '0'
,R === '1'
,Result === [11 * 0]
, 然后 Result 经过FillTenTimes
处理后变成[110 * 0]
,Dict[F]
为[]
,所以交给第四次递归的是Fill<'1', [110 * 0]>
; - 第四次递归时:
F === '1'
,R === ''
,Result === [110 * 0]
,然后 Result 经过FillTenTimes
处理后变成[1100 * 0]
,Dict[f]
为[0]
,所以交给第五次递归的是Fill<'', [1100 * 0, 0]>
; - 第五次递归时,由于传入的是一个空字符串,所以条件不满足,此时返回 Result,也就是
[1101 * 1]
。
PickByType
From
T
, pick a set of properties whose type are assignable toU
.
typescript
type PickByType<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface Model {
name: string
count: number
isReadonly: boolean
isEnable: boolean
}
type cases = [
Expect<Equal<PickByType<Model, boolean>, { isReadonly: boolean; isEnable: boolean }>>,
Expect<Equal<PickByType<Model, string>, { name: string }>>,
Expect<Equal<PickByType<Model, number>, { count: number }>>,
]
和 Pick 很类似,只不过是需要判断的是值:
typescript
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P]
}
StartsWith
Implement
StartsWith<T, U>
which takes two exact string types and returns whetherT
starts withU
typescript
type StartsWith<T extends string, U extends string> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<StartsWith<'abc', 'ac'>, false>>,
Expect<Equal<StartsWith<'abc', 'ab'>, true>>,
Expect<Equal<StartsWith<'abc', 'abcd'>, false>>,
Expect<Equal<StartsWith<'abc', ''>, true>>,
Expect<Equal<StartsWith<'abc', ' '>, false>>,
]
又是一道考验字符串操作的题:
typescript
type StartsWith<T extends string, U extends string> = T extends `${U}${infer R}`
? true
: false
EndsWith
Implement
EndsWith<T, U>
which takes two exact string types and returns whetherT
ends withU
typescript
type EndsWith<T extends string, U extends string> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<EndsWith<'abc', 'bc'>, true>>,
Expect<Equal<EndsWith<'abc', 'abc'>, true>>,
Expect<Equal<EndsWith<'abc', 'd'>, false>>,
]
和 StartsWith 一样,只需要变换一下位置:
typescript
type EndsWith<T extends string, U extends string> = T extends `${infer R}${U}`
? true
: false
PartialByKeys
Implement a generic
PartialByKeys<T, K>
which takes two type argumentT
andK
.
typescript
type PartialByKeys<T, K> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface User {
name: string
age: number
address: string
}
interface UserPartialName {
name?: string
age: number
address: string
}
interface UserPartialNameAndAge {
name?: string
age?: number
address: string
}
type cases = [
Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>,
Expect<Equal<PartialByKeys<User, 'name' | 'unknown'>, UserPartialName>>,
Expect<Equal<PartialByKeys<User, 'name' | 'age'>, UserPartialNameAndAge>>,
Expect<Equal<PartialByKeys<User>, Partial<User>>>,
]
将符合指定的 Key 的那些项转成可选项,我们可以分成两步来做:一是从源对象中移除指定的 Key(Omit);二是从源对象中取出指定的 Key (Extract)并将它们转成可选的:
typescript
// 1. 通过 Omit 移除指定的 Key
type RequiredObject<T, K extends PropertyKey = keyof T> = Omit<T, K>
// 2. 通过 Extract 过滤指定的 Key
type PatrialObject<T, K extends PropertyKey = keyof T> = {
[P in Extract<K, keyof T>]?: T[P]
}
然后再将两者合并即可:
typescript
type Merge<T> = Omit<T, never>
type PartialByKeys<T, K extends PropertyKey = keyof T> = Merge<Omit<T, K> & {
[P in Extract<K, keyof T>]?: T[P]
}>
RequiredByKeys
Implement a generic
RequiredByKeys<T, K>
which takes two type argumentT
andK
.
K
specify the set of properties ofT
that should set to be required. WhenK
is not provided, it should make all properties required just like the normalRequired<T>
.
typescript
type RequiredByKeys<T, K> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface User {
name?: string
age?: number
address?: string
}
interface UserRequiredName {
name: string
age?: number
address?: string
}
interface UserRequiredNameAndAge {
name: string
age: number
address?: string
}
type cases = [
Expect<Equal<RequiredByKeys<User, 'name'>, UserRequiredName>>,
Expect<Equal<RequiredByKeys<User, 'name' | 'unknown'>, UserRequiredName>>,
Expect<Equal<RequiredByKeys<User, 'name' | 'age'>, UserRequiredNameAndAge>>,
Expect<Equal<RequiredByKeys<User>, Required<User>>>,
]
这题的思路和 PartialByKeys 一样,但有点小区别:
typescript
type Merge<T> = Omit<T, never>
type RequiredByKeys<T, K = keyof T> = Merge<{
[P in keyof T as P extends K ? P : never]-?: T[P]
} & {
[P in keyof T as P extends K ? never : P]: T[P]
}>
Mutable
Implement the generic
Mutable<T>
which makes all properties inT
mutable (not readonly).
typescript
type Mutable<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface Todo1 {
title: string
description: string
completed: boolean
meta: {
author: string
}
}
type List = [1, 2, 3]
type cases = [
Expect<Equal<Mutable<Readonly<Todo1>>, Todo1>>,
Expect<Equal<Mutable<Readonly<List>>, List>>,
]
type errors = [
// @ts-expect-error
Mutable<'string'>,
// @ts-expect-error
Mutable<0>,
]
通过 -
号操作可以把 readonly 标识给移除,同时别忘了给泛型加约束处理掉 errors:
typescript
type Mutable<T extends Record<PropertyKey, any>> = {
-readonly [P in keyof T]: T[P]
}
OmitByType
From
T
, pick a set of properties whose type are not assignable toU
.
typescript
type OmitByType<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface Model {
name: string
count: number
isReadonly: boolean
isEnable: boolean
}
type cases = [
Expect<Equal<OmitByType<Model, boolean>, { name: string; count: number }>>,
Expect<Equal<OmitByType<Model, string>, { count: number; isReadonly: boolean; isEnable: boolean }>>,
Expect<Equal<OmitByType<Model, number>, { name: string; isReadonly: boolean; isEnable: boolean }>>,
]
只需要把值的类型做一次对比即可:
typescript
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P]
}
ObjectEntries
Implement the type version of
Object.entries
typescript
type ObjectEntries<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
interface Model {
name: string
age: number
locations: string[] | null
}
type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null]
type cases = [
Expect<Equal<ObjectEntries<Model>, ModelEntries>>,
Expect<Equal<ObjectEntries<Partial<Model>>, ModelEntries>>,
Expect<Equal<ObjectEntries<{ key?: undefined }>, ['key', undefined]>>,
Expect<Equal<ObjectEntries<{ key: undefined }>, ['key', undefined]>>,
]
这题需要注意两点,一个是把 T 转成 Required,另一个就是 undefined 的处理:
typescript
type ObjectEntries<T, R = Required<T>, K extends keyof R = keyof R> = K extends keyof R
? [K, R[K] extends undefined ? undefined : R[K]]
: never
Shift
Implement the type version of
Array.shift
typescript
type Shift<T> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<Shift<[3, 2, 1]>, [2, 1]>>,
Expect<Equal<Shift<['a', 'b', 'c', 'd']>, ['b', 'c', 'd']>>,
]
这题是比较简单的:
typescript
type Shift<T> = T extends [infer F, ...infer R]
? R
: never
Tuple to Nested Object
Given a tuple type
T
that only contains string type, and a typeU
, build an object recursively.
typescript
type TupleToNestedObject<T, U> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<TupleToNestedObject<['a'], string>, { a: string }>>,
Expect<Equal<TupleToNestedObject<['a', 'b'], number>, { a: { b: number } }>>,
Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, { a: { b: { c: boolean } } }>>,
Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>,
]
常规的数组递归操作即可:
typescript
type TupleToNestedObject<T, U> = T extends [infer F extends string, ...infer R]
? {
[P in F]: TupleToNestedObject<R, U>
}
: U