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

前端开发
2022年09月25日
1432

Hard 组(下)

DeepPick

Implement a type DeepPick, that extends Utility types Pick. A type takes two arguments.

typescript
type DeepPick = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Obj = { a: number b: string c: boolean obj: { d: number e: string f: boolean obj2: { g: number h: string i: boolean } } obj3: { j: number k: string l: boolean } } type cases = [ Expect<Equal<DeepPick<Obj, ''>, unknown>>, Expect<Equal<DeepPick<Obj, 'a'>, { a: number }>>, Expect<Equal<DeepPick<Obj, 'a' | ''>, { a: number } & unknown>>, Expect<Equal<DeepPick<Obj, 'a' | 'obj.e'>, { a: number } & { obj: { e: string } }>>, Expect<Equal<DeepPick<Obj, 'a' | 'obj.e' | 'obj.obj2.i'>, { a: number } & { obj: { e: string } } & { obj: { obj2: { i: boolean } } }>>, ]

我们可以看到题目中出现了 obj.eobj.obj2.i ,很明显,这需要通过 [Typed Get](#Typed Get) 来取值:

typescript
type Get<T, K extends string> = K extends `${infer Key}.${infer R}` ? Key extends keyof T ? Get<T[Key], R> : never : K extends keyof T ? T[K] : never

当然,我们需要对 Get 作一些调整,因为题中是需要一些新的对象作为值:

typescript
type Get<T, K extends string> = K extends `${infer Key}.${infer R}` ? Key extends keyof T ? { [P in keyof T as P extends Key ? P : never]: Get<T[Key], R> } : never : K extends keyof T ? { [P in keyof T as P extends K ? P : never ]: T[K] } : never

然后就是结果需要转成交叉类型:

typescript
/** * UnionToFunc<1 | 2> => ((arg: 1) => void | (arg: 2) => void) */ type UnionToFunc<T> = T extends unknown ? (arg: T) => void : never /** * UnionToIntersection<1 | 2> = 1 & 2 */ type UnionToIntersection<U> = UnionToFunc<U> extends (arg: infer Arg) => void ? Arg : never

最终组合起来即可:

typescript
type DeepPick<T, K extends string> = UnionToIntersection<Get<T, K>>

Pinia

Create a type-level function whose types is similar to Pinia library. You don’t need to implement function actually, just adding types.

typescript
declare function defineStore(store: unknown): unknown /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' const store = defineStore({ id: '', state: () => ({ num: 0, str: '', }), getters: { stringifiedNum() { // @ts-expect-error this.num += 1 return this.num.toString() }, parsedNum() { return parseInt(this.stringifiedNum) }, }, actions: { init() { this.reset() this.increment() }, increment(step = 1) { this.num += step }, reset() { this.num = 0 // @ts-expect-error this.parsedNum = 0 return true }, setNum(value: number) { this.num = value }, }, }) // @ts-expect-error store.nopeStateProp // @ts-expect-error store.nopeGetter // @ts-expect-error store.stringifiedNum() store.init() // @ts-expect-error store.init(0) store.increment() store.increment(2) // @ts-expect-error store.setNum() // @ts-expect-error store.setNum('3') store.setNum(3) const r = store.reset() type _tests = [ Expect<Equal<typeof store.num, number>>, Expect<Equal<typeof store.str, string>>, Expect<Equal<typeof store.stringifiedNum, string>>, Expect<Equal<typeof store.parsedNum, number>>, Expect<Equal<typeof r, true>>, ]

这题需要注意的是,getters 是只读的,同时在 getters 中的 state 也是只读的:

typescript
type StoreOptions<State, Getters, Actions> = { id?: string; state?: () => State; getters?: Getters & ThisType<Readonly<State> & { readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R ? R : never }>; actions?: Actions & ThisType<State & { readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R ? R : never } & Actions>; } declare function defineStore<State, Getters, Actions>(store: StoreOptions<State, Getters, Actions>): Readonly<State> & { readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R ? R : never } & Actions

Camelize

Implement Camelize which converts object from snake_case to to camelCase

typescript
type Camelize<T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal< Camelize<{ some_prop: string prop: { another_prop: string } array: [ { snake_case: string }, { another_element: { yet_another_prop: string } }, { yet_another_element: string }, ] }>, { someProp: string prop: { anotherProp: string } array: [ { snakeCase: string }, { anotherElement: { yetAnotherProp: string } }, { yetAnotherElement: string }, ] } >>, ]

首先需要实现一个将字符串转小陀峰的辅助类:

typescript
type CamelizeKey<K> = K extends `${infer F}_${infer R}` ? `${F}${CamelizeKey<Capitalize<R>>}` : K

然后区分 Tuple 和 Interface,逐一处理即可:

typescript
type Camelize<T> = T extends unknown[] ? T extends [infer F, ...infer R] ? [Camelize<F>, ...Camelize<R>] : [] : { [P in keyof T as CamelizeKey<P>]: T[P] extends object ? Camelize<T[P]> : T[P] }

Drop String

Drop the specified chars from a string.

typescript
type DropString<S, R> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<DropString<'butter fly!', ''>, 'butter fly!'>>, Expect<Equal<DropString<'butter fly!', ' '>, 'butterfly!'>>, Expect<Equal<DropString<'butter fly!', 'but'>, 'er fly!'>>, Expect<Equal<DropString<' b u t t e r f l y ! ', 'but'>, ' e r f l y ! '>>, Expect<Equal<DropString<' butter fly! ', ' '>, 'butterfly!'>>, Expect<Equal<DropString<' b u t t e r f l y ! ', ' '>, 'butterfly!'>>, Expect<Equal<DropString<' b u t t e r f l y ! ', 'but'>, ' e r f l y ! '>>, Expect<Equal<DropString<' b u t t e r f l y ! ', 'tub'>, ' e r f l y ! '>>, Expect<Equal<DropString<' b u t t e r f l y ! ', 'b'>, ' u t t e r f l y ! '>>, Expect<Equal<DropString<' b u t t e r f l y ! ', 't'>, ' b u e r f l y ! '>>, ]

需要一个 StringToUnion 的辅助类型:

typescript
type StringToUnion<S extends string> = S extends `${infer F}${infer R}` ? F | StringToUnion<R> : never

然后就是字符串操作:

typescript
type DropString<S, R extends string, U = StringToUnion<R>> = S extends `${infer F}${infer Rest}` ? F extends U ? DropString<Rest, R, U> : `${F}${DropString<Rest, R, U>}` : S

Split

The well known split() method splits a string into an array of substrings by looking for a separator, and returns the new array. The goal of this challenge is to split a string, by using a separator, but in the type system!

typescript
type Split<S extends string, SEP extends string> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Split<'Hi! How are you?', 'z'>, ['Hi! How are you?']>>, Expect<Equal<Split<'Hi! How are you?', ' '>, ['Hi!', 'How', 'are', 'you?']>>, Expect<Equal<Split<'Hi! How are you?', ''>, ['H', 'i', '!', ' ', 'H', 'o', 'w', ' ', 'a', 'r', 'e', ' ', 'y', 'o', 'u', '?']>>, Expect<Equal<Split<'', ''>, []>>, Expect<Equal<Split<'', 'z'>, ['']>>, Expect<Equal<Split<string, 'whatever'>, string[]>>, ]

字符串分隔,也是比较简单的操作:

typescript
type Split<S extends string, SEP extends string> = SEP extends '' ? S extends `${infer F}${infer R}` ? [F, ...Split<R, SEP>] : [] : SEP extends S ? S[] : S extends `${infer F}${SEP}${infer R}` ? [F, ...Split<R, SEP>] : [S]

ClassPublicKeys

Implement the generic ClassPublicKeys<T> which returns all public keys of a class.

typescript
type ClassPublicKeys = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' class A { public str: string protected num: number private bool: boolean constructor() { this.str = 'naive' this.num = 19260917 this.bool = true } getNum() { return Math.random() } } type cases = [ Expect<Equal<ClassPublicKeys<A>, 'str' | 'getNum'>>, ]

只需要 keyof 即可:

typescript
type ClassPublicKeys<T> = keyof T

IsRequiredKey

Implement a generic IsRequiredKey<T, K> that return whether K are required keys of T .

typescript
type IsRequiredKey<T, K extends keyof T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsRequiredKey<{ a: number; b?: string }, 'a'>, true>>, Expect<Equal<IsRequiredKey<{ a: number; b?: string }, 'b'>, false>>, Expect<Equal<IsRequiredKey<{ a: number; b?: string }, 'b' | 'a'>, false>>, ]

只需要利用 [Required Keys](#Required Keys) 的知识即可:

typescript
type RequiredKeys<T> = keyof { [P in keyof T as Omit<T, P> extends T ? never : P]: T[P] } type IsRequiredKey<T, K extends keyof T> = Equal<RequiredKeys<T>, K>

ObjectFromEntries

Implement the type version of Object.fromEntries

typescript
type ObjectFromEntries<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<ObjectFromEntries<ModelEntries>, Model>>, ]

我的想法很简单,先把联合类型转成二维数组,然后就是普通的数组操作收集结果即可:

typescript
/** * UnionToFunc<1 | 2> => ((arg: 1) => void | (arg: 2) => void) */ type UnionToFunc<T> = T extends unknown ? (arg: T) => void : never /** * UnionToIntersection<1 | 2> = 1 & 2 */ type UnionToIntersection<U> = UnionToFunc<U> extends (arg: infer Arg) => void ? Arg : never /** * LastInUnion<1 | 2> = 2 */ type LastInUnion<U> = UnionToIntersection<UnionToFunc<U>> extends (x: infer L) => void ? L : never type UnionToTuple<T, L = LastInUnion<T>> = [L] extends [never] ? [] : [...UnionToTuple<Exclude<T, L>>, L] type ObjectFromEntries<T, D = UnionToTuple<T>, Result extends Record<PropertyKey, any> = {}> = D extends [infer F, ...infer R] ? F extends [infer FF extends string, infer FR] ? ObjectFromEntries< never, R, { [K in FF | keyof Result]: K extends FF ? FR : K extends keyof Result ? Result[K] : never } > : never : Result

IsPalindrome

Implement type IsPalindrome<T> to check whether a string or number is palindrome.

typescript
type IsPalindrome<T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<IsPalindrome<'abc'>, false>>, Expect<Equal<IsPalindrome<'b'>, true>>, Expect<Equal<IsPalindrome<'abca'>, false>>, Expect<Equal<IsPalindrome<'abcba'>, true>>, Expect<Equal<IsPalindrome<121>, true>>, Expect<Equal<IsPalindrome<19260817>, false>>, ]

首先我们需要把数字转成字符串:

typescript
type NumberToString<N extends number | string> = `${N}` extends `${infer S}` ? S : N

然后就是头尾对比即可:

typescript
type IsPalindrome<T extends number | string, S = NumberToString<T>> = S extends `${infer F}${infer R}` ? F extends S ? true : R extends `${infer RR}${F}` ? IsPalindrome<never, RR> : false : false

Mutable Keys

Implement the advanced util type MutableKeys, which picks all the mutable (not readonly) keys into a union.

typescript
type MutableKeys<T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MutableKeys<{ a: number; readonly b: string }>, 'a'>>, Expect<Equal<MutableKeys<{ a: undefined; readonly b: undefined }>, 'a'>>, Expect<Equal<MutableKeys<{ a: undefined; readonly b?: undefined; c: string; d: null }>, 'a' | 'c' | 'd'>>, Expect<Equal<MutableKeys<{}>, never>>, ]

只需要在取键时拿 Readonly 包裹后的值对比即可:

typescript
type MutableKeys<T> = keyof { [P in keyof T as Equal<{ [K in P]: T[K] }, Readonly<{ [K in P]: T[K] }>> extends false ? P: never]: T[P] }

Intersection

Implement the type version of Lodash.intersection with a little difference. Intersection takes an Array T containing several arrays or any type element including the union type, and returns a new union containing all intersection elements.

typescript
type Intersection<T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Intersection<[[1, 2], [2, 3], [2, 2]]>, 2>>, Expect<Equal<Intersection<[[1, 2, 3], [2, 3, 4], [2, 2, 3]]>, 2 | 3>>, Expect<Equal<Intersection<[[1, 2], [3, 4], [5, 6]]>, never>>, Expect<Equal<Intersection<[[1, 2, 3], [2, 3, 4], 3]>, 3>>, Expect<Equal<Intersection<[[1, 2, 3], 2 | 3 | 4, 2 | 3]>, 2 | 3>>, Expect<Equal<Intersection<[[1, 2, 3], 2, 3]>, never>>, ]

这题可以利用 Extract 来获取交集,不过需要注意的一点是:

typescript
type A = Extract<1 | 2, unknown> // 1 | 2 type B = Extract<1 | 2, never> // never type C = Extract<unknown, 1 | 2> // never

答案如下:

typescript
type Intersection<T> = T extends [infer F, ...infer R] ? F extends unknown[] ? Extract<F[number], Intersection<R>> : Extract<F, Intersection<R>> : unknown

Binary to Decimal

Implement BinaryToDecimal<S> which takes an exact string type S consisting 0 and 1 and returns an exact number type corresponding with S when S is regarded as a binary.

You can assume that the length of S is equal to or less than 8 and S is not empty.

typescript
type BinaryToDecimal<S extends string> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<BinaryToDecimal<'10'>, 2>>, Expect<Equal<BinaryToDecimal<'0011'>, 3>>, Expect<Equal<BinaryToDecimal<'00000000'>, 0>>, Expect<Equal<BinaryToDecimal<'11111111'>, 255>>, Expect<Equal<BinaryToDecimal<'10101010'>, 170>>, ]

这题还是比较简单的,我们之前实现过快速生成指定长度数组的方法:

typescript
type NumberToArray<N extends number, Result extends 0[] = []> = Result['length'] extends N ? Result : NumberToArray<N, [...Result, 0]> type GetTwice<T extends unknown[]> = [ ...T, ...T ] type BinaryToDecimal<S extends string, Result extends unknown[] = []> = S extends `${infer F extends number}${infer R}` ? BinaryToDecimal<R, [...GetTwice<Result>, ...NumberToArray<F>]> : Result['length']

Object Key Paths

Get all possible paths that could be called by _.get (a lodash function) to get the value of an object

typescript
type ObjectKeyPaths<T extends object> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect, ExpectExtends } from '@type-challenges/utils' const ref = { count: 1, person: { name: 'cattchen', age: 22, books: ['book1', 'book2'], pets: [ { type: 'cat', }, ], }, } type cases = [ Expect<Equal<ObjectKeyPaths<{ name: string; age: number }>, 'name' | 'age'>>, Expect< Equal< ObjectKeyPaths<{ refCount: number person: { name: string; age: number } }>, 'refCount' | 'person' | 'person.name' | 'person.age' > >, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'count'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.name'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.age'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.books'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.pets'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.books.0'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.books.1'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.books[0]'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.books.[0]'>>, Expect<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.pets.0.type'>>, Expect<Equal<ExpectExtends<ObjectKeyPaths<typeof ref>, 'notExist'>, false>>, Expect<Equal<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.notExist'>, false>>, Expect<Equal<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.name.'>, false>>, Expect<Equal<ExpectExtends<ObjectKeyPaths<typeof ref>, '.person.name'>, false>>, Expect<Equal<ExpectExtends<ObjectKeyPaths<typeof ref>, 'person.pets.[0]type'>, false>>, ]

首先需要一个辅助类型来实现路径拼接,需要注意的是路径有 a.ba.[0]a[0] 这几种款式:

typescript
type GetPath<K extends PropertyKey & (string | number), Prefix extends string = ''> = [Prefix] extends [never] ? `${K}` : K extends number ? `${Prefix}.${K}` | `${Prefix}[${K}]` | `${Prefix}.[${K}]` : `${Prefix}.${K}`

接下来是对对象进行深度递归并取出它们的 key 作为最终结果:

typescript
type ObjectKeyPaths<T extends object, Result extends string = never> = Result | { [P in keyof T & (string | number)]: T[P] extends object ? ObjectKeyPaths<T[P], GetPath<P, Result>> : GetPath<P, Result> }[keyof T & (string | number)]

Two Sum

Given an array of integers nums and an integer target, return true if two numbers such that they add up to target.

typescript
type TwoSum<T extends number[], U extends number> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TwoSum<[3, 3], 6>, true>>, Expect<Equal<TwoSum<[3, 2, 4], 6>, true>>, Expect<Equal<TwoSum<[2, 7, 11, 15], 15>, false>>, Expect<Equal<TwoSum<[2, 7, 11, 15], 9>, true>>, Expect<Equal<TwoSum<[1, 2, 3], 0>, false>>, Expect<Equal<TwoSum<[1, 2, 3], 1>, false>>, Expect<Equal<TwoSum<[1, 2, 3], 2>, false>>, Expect<Equal<TwoSum<[1, 2, 3], 3>, true>>, Expect<Equal<TwoSum<[1, 2, 3], 4>, true>>, Expect<Equal<TwoSum<[1, 2, 3], 5>, true>>, Expect<Equal<TwoSum<[1, 2, 3], 6>, false>>, ]

这题需要一些辅助函数:

typescript
/** * NumberToArray<1> // [0] * NumberToArray<2> // [0, 0] */ type NumberToArray<N extends number, Result extends 0[] = []> = Result['length'] extends N ? Result : NumberToArray<N, [0, ...Result]> /** * Sum<1, 2> // 3 */ type Sum<A extends number, B extends number> = [...NumberToArray<A>, ...NumberToArray<B>]['length'] /** * EachSum<[1, 2], 3> // [4, 5] */ type EachSum<T extends number[], N extends number, Result extends number[] = []> = T extends [infer F extends number, ...infer R extends number[]] ? EachSum<R, N, [...Result, Sum<F, N> & number]> : Result /** * GetResult<[1, 2, 3]> // [3, 4, 5] */ type GetResult<T extends number[], Result extends number[] = []> = T extends [infer F extends number, ...infer R extends number[]] ? GetResult<R, [...Result, ...EachSum<R, F>]> : Result

然后就是调用即可:

typescript
type TwoSum<T extends number[], U extends number> = U extends GetResult<T>[number] ? true : false

ValidDate

Implement a type ValidDate, which takes an input type T and returns whether T is a valid date.

typescript
type ValidDate<T extends string> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ValidDate<'0102'>, true>>, Expect<Equal<ValidDate<'0131'>, true>>, Expect<Equal<ValidDate<'1231'>, true>>, Expect<Equal<ValidDate<'0229'>, false>>, Expect<Equal<ValidDate<'0100'>, false>>, Expect<Equal<ValidDate<'0132'>, false>>, Expect<Equal<ValidDate<'1301'>, false>>, Expect<Equal<ValidDate<'0123'>, true>>, Expect<Equal<ValidDate<'01234'>, false>>, Expect<Equal<ValidDate<''>, false>>, ]

这题我用了一车的辅助类型来完成:

typescript
/** * StringToNumber<'01'> // 1 * StringToNumber<'1'> // 1 * SttringToNumber<''> // 0 */ type StringToNumber<T extends string> = T extends `0${infer R extends number}` ? R : T extends `${infer R extends number}` ? R : 0 type PlusOne<T extends number, R extends 0[] = []> = R['length'] extends T ? [0, ...R]['length'] : PlusOne<T, [0, ...R]> /** * GetDateAndMonth<'0'> // [0, 0] * GetDateAndMonth<'0123'> // [1, 23] * GetDateAndMonth<'01234'> // [1, 234] */ type GetDateAndMonth<T extends string, C extends number = 0, Date extends string = '', Month extends string = ''> = C extends 2 ? [StringToNumber<Date>, StringToNumber<T>] : T extends `${infer F}${infer R}` ? GetDateAndMonth<R, PlusOne<C>, `${Date}${F}`> : [StringToNumber<Date>, StringToNumber<Month>] 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> /** * GreaterThan<1, 2> // false * GreaterThan<2, 2> // false * GreaterThan<3, 2> // true */ type GreaterThan<T extends number, U extends number> = Equal<T, U> extends true ? false : GT<T, U> /** * InRange<2, 1, 2> // false * InRange<2, 1, 3> // true */ type InRange<A extends number, F extends number, R extends number> = GreaterThan<A, F> extends true ? GreaterThan<R, A> extends true ? true : false : false

最终:

typescript
type ValidDate<T extends string, A extends [number, number] = GetDateAndMonth<T>> = InRange<A[0], 0, 13> extends true ? A[0] extends 2 ? InRange<A[1], 0, 29> extends true ? true : false : InRange<A[1], 0, 32> extends true ? true : false : false

Assign

You have a target object and a source array of objects. You need to copy property from source to target, if it has the same property as the source, you should always keep the source property, and drop the target property. (Inspired by the Object.assign API)

typescript
type Assign<T extends Record<string, unknown>, U> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' // case1 type Case1Target = {} type Case1Origin1 = { a: 'a' } type Case1Origin2 = { b: 'b' } type Case1Origin3 = { c: 'c' } type Case1Answer = { a: 'a' b: 'b' c: 'c' } // case2 type Case2Target = { a: [1, 2, 3] } type Case2Origin1 = { a: { a1: 'a1' } } type Case2Origin2 = { b: [2, 3, 3] } type Case2Answer = { a: { a1: 'a1' } b: [2, 3, 3] } // case3 type Case3Target = { a: 1 b: ['b'] } type Case3Origin1 = { a: 2 b: { b: 'b' } c: 'c1' } type Case3Origin2 = { a: 3 c: 'c2' d: true } type Case3Answer = { a: 3 b: { b: 'b' } c: 'c2' d: true } // case 4 type Case4Target = { a: 1 b: ['b'] } type Case4Answer = { a: 1 b: ['b'] } type cases = [ Expect<Equal<Assign<Case1Target, [Case1Origin1, Case1Origin2, Case1Origin3]>, Case1Answer>>, Expect<Equal<Assign<Case2Target, [Case2Origin1, Case2Origin2]>, Case2Answer>>, Expect<Equal<Assign<Case3Target, [Case3Origin1, Case3Origin2]>, Case3Answer>>, Expect<Equal<Assign<Case4Target, ['', 0]>, Case4Answer>>, ]

这题还是比较简单的,首先需要把不符合的数组项给过滤掉:

typescript
type AllowArgs<T extends unknown[], Result extends Record<string, unknown>[] = []> = T extends [infer F, ...infer R] ? F extends Record<string, unknown> ? AllowArgs<R, [...Result, F]> : AllowArgs<R, Result> : Result

然后就是逐个处理数组的每一项进行合并即可:

typescript
type MergeInterface< T extends Record<string, unknown>, D extends Record<string, unknown> > = { [P in keyof T | keyof D]: P extends keyof D ? D[P] : P extends keyof T ? T[P] : never } type Assign< T extends Record<string, unknown>, U extends unknown[], D extends Record<string, unknown>[] = AllowArgs<U> > = D extends [infer F extends Record<string, unknown>, ...infer R extends Record<string, unknown>[]] ? Assign<MergeInterface<T, F>, never, R> : T

这里需要注意的是,后面的键要覆盖前面相同的键。

Capitalize Nest Object Keys

Capitalize the key of the object, and if the value is an array, iterate through the objects in the array.

typescript
type CapitalizeNestObjectKeys<T> = any /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' import { ExpectFalse, NotEqual } from '@type-challenges/utils' type foo = { foo: string bars: [{ foo: string }] } type Foo = { Foo: string Bars: [{ Foo: string }] } type cases = [ Expect<Equal<Foo, CapitalizeNestObjectKeys<foo>>>, ]

这题也比较容易处理:

typescript
type EachCapitalize<T extends unknown[]> = T extends [infer F, ...infer R] ? [CapitalizeNestObjectKeys<F>, ...EachCapitalize<R>] : [] type CapitalizeNestObjectKeys<T> = { [K in keyof T as K extends string ? Capitalize<K> : never]: T[K] extends unknown[] ? EachCapitalize<T[K]> : T[K] extends Record<string, unknown> ? CapitalizeNestObjectKeys<T[K]> : T[K] }

Run-length encoding

Given a string sequence of a letters f.e. AAABCCXXXXXXY. Return run-length encoded string 3AB2C6XY.

Also make a decoder for that string.

typescript
namespace RLE { export type Encode<S extends string> = any export type Decode<S extends string> = any } /* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ // Raw string -> encoded string Expect<Equal<RLE.Encode<'AAABCCXXXXXXY'>, '3AB2C6XY'>>, // Encoded string -> decoded string Expect<Equal<RLE.Decode<'3AB2C6XY'>, 'AAABCCXXXXXXY'>>, ]

首先是 Encode 的处理,借助一个缓存的数组来完成相同字符的收集:

typescript
type Encode< S extends string, Cache extends string[] = [], Result extends string = '' > = S extends `${infer F}${infer R}` ? Cache extends [] ? Encode<R, [...Cache, F], Result> : Cache[0] extends F ? Encode<R, [...Cache, F], Result> : Encode<R, [F], `${Result}${Cache['length'] extends 1 ? '' : Cache['length']}${Cache[0]}`> : `${Result}${Cache[0]}`

接下来是 Decode 的处理,需要一些辅助类型:

typescript
/** * ParseInt<'A'> // never * ParseInt<'1'> // 1 */ type ParseInt<T extends string> = T extends `${infer R extends number}` ? R : never /** * FillString<'A', 0> // '' * FillString<'A', 1> // 'A' * FillString<'A', 3> // 'AAA' */ type FillString<T extends string, L extends number, R extends T[] = [], To extends string = ''> = R['length'] extends L ? To : FillString<T, L, [T, ...R], `${To}${T}`>

最后就是收集 Number 用于填充即可:

typescript
type Decode< S extends string, N extends number = never, Result extends string = '' > = S extends `${infer F}${infer R}` ? [ParseInt<F>] extends [never] ? [N] extends [never] ? Decode<R, never, `${Result}${F}`> : Decode<R, never, `${Result}${FillString<F, N>}`> : Decode<R, ParseInt<F>, Result> : Result

完整答案如下:

typescript
/** * ParseInt<'A'> // never * ParseInt<'1'> // 1 */ type ParseInt<T extends string> = T extends `${infer R extends number}` ? R : never /** * FillString<'A', 0> // '' * FillString<'A', 1> // 'A' * FillString<'A', 3> // 'AAA' */ type FillString<T extends string, L extends number, R extends T[] = [], To extends string = ''> = R['length'] extends L ? To : FillString<T, L, [T, ...R], `${To}${T}`> namespace RLE { export type Encode< S extends string, Cache extends string[] = [], Result extends string = '' > = S extends `${infer F}${infer R}` ? Cache extends [] ? Encode<R, [...Cache, F], Result> : Cache[0] extends F ? Encode<R, [...Cache, F], Result> : Encode<R, [F], `${Result}${Cache['length'] extends 1 ? '' : Cache['length']}${Cache[0]}`> : `${Result}${Cache[0]}` export type Decode< S extends string, N extends number = never, Result extends string = '' > = S extends `${infer F}${infer R}` ? [ParseInt<F>] extends [never] ? [N] extends [never] ? Decode<R, never, `${Result}${F}`> : Decode<R, never, `${Result}${FillString<F, N>}`> : Decode<R, ParseInt<F>, Result> : Result }