小镇做题家 - TypeScript 类型大挑战(困难篇 - 下)
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.e
、obj.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 whetherK
are required keys ofT
.
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 typeS
consisting 0 and 1 and returns an exact number type corresponding withS
whenS
is regarded as a binary.You can assume that the length of
S
is equal to or less than 8 andS
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.b
、a.[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 integertarget
, return true if two numbers such that they add up totarget
.
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 string3AB2C6XY
.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
}