小镇做题家 - TypeScript 类型大挑战(困难篇 - 上)
## Hard 组(上)
困难级别的题,能做两道都算我赢!
### Simple Vue
> Implement a simpiled version of a Vue-like typing support.
>
> By providing a function name `SimpleVue` (similar to `Vue.extend` or `defineComponent`), it should properly infer the `this` type inside computed and methods.
>
> In this challenge, we assume that SimpleVue take an Object with `data`, `computed` and `methods` fields as it's only argument,
>
> - `data` is a simple function that returns an object that exposes the context `this`, but you won't be accessible to other computed values or methods.
> - `computed` is an Object of functions that take the context as `this`, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.
> - `methods` is an Object of functions that take the context as `this` as well. Methods can access the fields exposed by `data`, `computed` as well as other `methods`. The different between `computed` is that `methods` exposed as functions as-is.
>
> The type of `SimpleVue`'s return value can be arbitrary.
```typescript
declare function SimpleVue(options: any): any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
SimpleVue({
data() {
// @ts-expect-error
this.firstname
// @ts-expect-error
this.getRandom()
// @ts-expect-error
this.data()
return {
firstname: 'Type',
lastname: 'Challenges',
amount: 10,
}
},
computed: {
fullname() {
return `${this.firstname} ${this.lastname}`
},
},
methods: {
getRandom() {
return Math.random()
},
hi() {
alert(this.amount)
alert(this.fullname.toLowerCase())
alert(this.getRandom())
},
test() {
const fullname = this.fullname
const cases: [Expect>] = [] as any
},
},
})
```
这道题考验的是 [ThisType](https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype) 的应用:
```typescript
type OptionsType = {
data?: () => Data;
computed?: Computed & ThisType infer R
? R
: never
}>;
methods?: Methods & ThisType infer R
? R
: never
} & Methods>;
}
declare function SimpleVue(options: OptionsType): any
```
### Currying
> TypeScript 4.0 is recommended in this challenge
>
> [Currying](https://en.wikipedia.org/wiki/Currying) is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
```typescript
declare function Currying(fn: any): any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const curried1 = Currying((a: string, b: number, c: boolean) => true)
const curried2 = Currying((a: string, b: number, c: boolean, d: boolean, e: boolean, f: string, g: boolean) => true)
type cases = [
Expect (b: number) => (c: boolean) => true
>>,
Expect (b: number) => (c: boolean) => (d: boolean) => (e: boolean) => (f: string) => (g: boolean) => true
>>,
]
```
首先,我们可以确认的是最终的返回值:
```typescript
declare function Currying(fn: T): T extends (...args: any) => infer Return
? () => Return
: never
```
并且,可以看到,最后一个函数的参数是我们给 `Currying()` 传入的回调函数中的最后一个参数:
```typescript
declare function Last(fn: T): T extends (...args: [...infer R, infer L]) => infer Return
? (arg: L) => Return
: never
const a = Last((a: number, b: string) => true) // (arg: string) => true
```
之后我们就需要做一些递归操作,直到把参数使用完毕:
```typescript
type GetLast any> = ArgsArry extends [...infer R, infer L]
? GetLast T>
: T
```
所以最终的结果是:
```typescript
type CurryingReturnType any> = D extends [...infer R, infer L]
? CurryingReturnType T>
: T
declare function Currying(fn: T): T extends (...args: [...infer R, infer L]) => infer Return
? CurryingReturnType Return>
: never
```
### Union to Intersection
> Implement the advanced util type `UnionToIntersection`
```typescript
type UnionToIntersection = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'foo' & 42 & true>>,
Expect 'foo') | ((i: 42) => true)>, (() => 'foo') & ((i: 42) => true)>>,
]
```
这一题,我们需要利用到以下的知识点,来源于 [https://github.com/Microsoft/TypeScript/pull/21496](https://github.com/Microsoft/TypeScript/pull/21496) :
```typescript
// Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
type Unpacked =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise ? U :
T;
type T0 = Unpacked; // string
type T1 = Unpacked; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked>; // string
type T4 = Unpacked[]>; // Promise
type T5 = Unpacked[]>>; // string
// Note that is not possible for a conditional type to recursively reference itself, as might be desired in the Unpacked case above. We're still considering ways in which to implement this.
// The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:
type Foo = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>; // string
type T11 = Foo<{ a: string, b: number }>; // string | number
// Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
type Bar = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number
```
我们首要做的是,将联合类型转成函数形式的联合类型:
```typescript
type ToFunc = T extends any
? (arg: T) => void
: never
type F = ToFunc<'foo' | 42 | true>
// F = (arg: 'foo') => void | (arg: 42) => void | (arg: true) => void
```
然后再利用:在逆变位置的同一类型变量中的多个候选会被推断成交叉类型。【函数参数是逆变的,而对象属性是协变的。】这个特性来把参数变成交叉类型:
```typescript
type UnionToIntersection = ToFunc extends (arg: infer Arg) => void
? Arg
: never
```
### Get Required
> Implement the advanced util type `GetRequired`, which remains all the required fields
```typescript
type GetRequired = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, { foo: number }>>,
Expect, { foo: undefined }>>,
]
```
我们先做个试验:
```typescript
type A = { foo: number; bar?: string }
type AE = Expect>> // false
type B = { foo: number; bar: string }
type BE = Expect>> // true
type C = { foo: number }
type CE = Expect>> // true
type D = { bar?: string }
type DE = Expect>> // false
```
也就是说,当 T 中所有键都是必需的键时,才会和通过 `Required` 包装后的对象相等,所以我们可以用这个方式来逐一排除掉非必需的键:
```typescript
type GetRequired = {
[P in keyof T as { [K in P]: T[K] } extends Required<{ [K in P]: T[K] }> ? P : never]: T[P]
}
```
### Get Optional
> Implement the advanced util type `GetOptional`, which remains all the optional fields
```typescript
type GetOptional = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, { bar?: string }>>,
Expect, { bar?: undefined }>>,
]
```
这题的取值和 [Get Required](#Get Required) 反过来即可:
```typescript
type GetOptional = {
[P in keyof T as { [K in P]: T[K] } extends Required<{ [K in P]: T[K] }> ? never : P]: T[P]
}
```
### Required Keys
> Implement the advanced util type `RequiredKeys`, which picks all the required keys into a union.
```typescript
type RequiredKeys = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'a'>>,
Expect, 'a'>>,
Expect, 'a' | 'c' | 'd'>>,
Expect, never>>,
]
```
这个解题只需要在 [Get Required](#Get Required) 的返回值前面加上 keyof 即可:
```typescript
type RequiredKeys = keyof {
[P in keyof T as Omit extends T ? never : P]: T[P]
}
```
### Optional Keys
> Implement the advanced util type `OptionalKeys`, which picks all the optional keys into a union.
```typescript
type OptionalKeys = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'b'>>,
Expect, 'b'>>,
Expect, 'b' | 'c' | 'd'>>,
Expect, never>>,
]
```
keyof [Get Optional](#Get Optional) 即可:
```typescript
type OptionalKeys = keyof {
[P in keyof T as Omit extends T ? P : never]: T[P]
}
```
### Capitalize Words
> Implement `CapitalizeWords` which converts the first letter of **each word of a string** to uppercase and leaves the rest as-is.
```typescript
type CapitalizeWords = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'Foobar'>>,
Expect, 'FOOBAR'>>,
Expect, 'Foo Bar'>>,
Expect, 'Foo Bar Hello World'>>,
Expect, 'Foo Bar.Hello,World'>>,
Expect, 'Aa!Bb@Cc#Dd$Ee%Ff^Gg&Hh*Ii(Jj)Kk_Ll+Mm{Nn}Oo|Pp.Qq'>>,
Expect, ''>>,
]
```
从题意可知,只要前一个字符不是字母,那么它就得转成大写:
```typescript
type Alphabet = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G'
| 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P'
| 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'
type CapitalizeWords<
S extends string,
Prev extends string = '',
Result extends string = ''
> = S extends `${infer F}${infer R}`
? Uppercase extends Alphabet
? CapitalizeWords
: CapitalizeWords}`>
: Result
// type True = Expect extends Uppercase<'.'> ? true : false, true>>
// type False = Expect extends Uppercase<'A'> ? true : false, false>>
```
### CamelCase
> Implement `CamelCase` which converts `snake_case` string to `camelCase`.
```typescript
type CamelCase = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'foobar'>>,
Expect, 'foobar'>>,
Expect, 'fooBar'>>,
Expect, 'fooBarHelloWorld'>>,
Expect, 'helloWorldWithTypes'>>,
Expect, '-'>>,
Expect, ''>>,
Expect, '.'>>,
]
```
暴力解题法:
```typescript
type NoAphabet = T extends `${infer F}${infer R}`
? Lowercase extends Uppercase
? NoAphabet
: false
: true
type ToCamelCase<
S extends string,
Bool extends boolean = false,
Result extends string = ''
> = S extends `${infer F}${infer R}`
? Lowercase extends Uppercase
? ToCamelCase
: Bool extends true
? ToCamelCase}`>
: ToCamelCase}`>
: Result
type CamelCase<
S extends string
> = NoAphabet extends true ? S : ToCamelCase
```
利用工具类解题法,`Uncapitalize` 把首字母变成小写,`Capitalize` 把首字母变成大写:
```typescript
type CamelCase = S extends `${infer F}${infer R}`
? IsFirst extends true
? CamelCase, Result, false>
: F extends '_'
? CamelCase, Result, false>
: CamelCase, `${Result}${F}`, false>
: Result
```
### C-printf Parser
> There is a function in C language: `printf`. This function allows us to print something with formatting. Like this:
>
> ```c
> printf("The result is %d.", 42);
> ```
>
> This challenge requires you to parse the input string and extract the format placeholders like `%d` and `%f`. For example, if the input string is `"The result is %d."`, the parsed result is a tuple `['dec']`.
>
> Here is the mapping:
>
> ```typescript
> type ControlsMap = {
> c: 'char',
> s: 'string',
> d: 'dec',
> o: 'oct',
> h: 'hex',
> f: 'float',
> p: 'pointer',
> }
> ```
```typescript
type ControlsMap = {
c: 'char'
s: 'string'
d: 'dec'
o: 'oct'
h: 'hex'
f: 'float'
p: 'pointer'
}
type ParsePrintFormat = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, []>>,
Expect, []>>,
Expect, ['dec']>>,
Expect, []>>,
Expect, ['dec']>>,
Expect, ['float']>>,
Expect, ['hex']>>,
Expect, []>>,
Expect, ['string', 'dec']>>,
Expect, []>>,
]
```
这题还是比较简单的,只需要逐个对比,遇到 `%` 时就记录起来即可:
```typescript
type ParsePrintFormat = S extends `${infer F}${infer R}`
? Prev extends '%'
? F extends keyof ControlsMap
? ParsePrintFormat
: ParsePrintFormat
: F extends '%'
? ParsePrintFormat
: ParsePrintFormat
: Result
```
### Vue Basic Props
> **This challenge continues from [Simple Vue](#Simple Vue), you should finish that one first, and modify your code based on it to start this challenge**.
>
> In addition to the Simple Vue, we are now having a new `props` field in the options. This is a simplified version of Vue's `props` option. Here are some of the rules.
>
> `props` is an object containing each field as the key of the real props injected into `this`. The injected props will be accessible in all the context including `data`, `computed`, and `methods`.
>
> A prop will be defined either by a constructor or an object with a `type` field containing constructor(s).
```typescript
declare function VueBasicProps (options: any) : any
/* _____________ Test Cases _____________ */
import type { Debug, Equal, Expect, IsAny } from '@type-challenges/utils'
class ClassA {}
VueBasicProps({
props: {
propA: {},
propB: { type: String },
propC: { type: Boolean },
propD: { type: ClassA },
propE: { type: [String, Number] },
propF: RegExp,
},
data(this) {
type PropsType = Debug
type cases = [
Expect>,
Expect>,
Expect>,
Expect>,
Expect>,
Expect>,
]
// @ts-expect-error
this.firstname
// @ts-expect-error
this.getRandom()
// @ts-expect-error
this.data()
return {
firstname: 'Type',
lastname: 'Challenges',
amount: 10,
}
},
computed: {
fullname() {
return `${this.firstname} ${this.lastname}`
},
},
methods: {
getRandom() {
return Math.random()
},
hi() {
alert(this.fullname.toLowerCase())
alert(this.getRandom())
},
test() {
const fullname = this.fullname
const propE = this.propE
type cases = [
Expect>,
Expect>,
]
},
},
})
```
首先我们把 Simple Vue 中的实现代码复制过来:
```typescript
type OptionsType = {
data?: () => Data;
computed?: Computed & ThisType infer R
? R
: never
}>;
methods?: Methods & ThisType infer R
? R
: never
} & Methods>;
}
declare function VueBasicProps(options: OptionsType): any
```
然后需要处理 Props:
```typescript
class A {}
const props = {
// {}
propA: {},
// { type: StringContructor }
propB: { type: String },
// { type: BooleanConstructor }
propC: { type: Boolean },
// { type: ClassA }
propD: { type: ClassA },
// { type: (StringConstructor | NumberConstructor)[] }
propE: { type: [String, Number] },
// RegExpConstructor
propF: RegExp,
}
```
也就是说 Props 中的值有以下几种情况:
```typescript
type Constructor = new (...args: any) => any
type PropsValue = Constructor | { type?: Constructor | Constructor[] }
type OptionsType, Data, Computed, Methods> = {
props?: Props;
...
}
```
接下来,我们需要一个可以通过构造器来获取类型的类型:
```typescript
type ConstructorMap = T extends undefined
? any
: T extends StringConstructor
? string
: T extends NumberConstructor
? number
: T extends RegExpConstructor
? RegExp
: T extends BooleanConstructor
? boolean
: T extends Constructor[]
? ConstructorMap
: T extends { prototype: infer P }
? P
: any
```
然后再创建一个类型用于获取 Props 对象:
```typescript
type PropsContext = {
[K in keyof T]: T[K] extends { type: infer R }
? ConstructorMap
: ConstructorMap
}
```
最后,把 PropsContext 加入到指定位置即可:
```typescript
type Constructor = new (...args: any) => any
type PropsValue = Constructor | { type?: Constructor | Constructor[] }
type ConstructorMap = T extends undefined
? any
: T extends StringConstructor
? string
: T extends NumberConstructor
? number
: T extends RegExpConstructor
? RegExp
: T extends BooleanConstructor
? boolean
: T extends Constructor[]
? ConstructorMap
: T extends { prototype: infer P }
? P
: any
type PropsContext = {
[K in keyof T]: T[K] extends { type: infer R }
? ConstructorMap
: ConstructorMap
}
type OptionsType, Data, Computed, Methods> = {
props?: Props,
data?: (this: PropsContext) => Data;
computed?: Computed & ThisType infer R
? R
: never
} & PropsContext>;
methods?: Methods & ThisType & Data & {
[P in keyof Computed]: Computed[P] extends (...args: any) => infer R
? R
: never
} & Methods>;
}
declare function VueBasicProps, Data, Computed, Methods>(options: OptionsType): any
```
### IsAny
> Sometimes it's useful to detect if you have a value with `any` type. This is especially helpful while working with third-party Typescript modules, which can export `any` values in the module API. It's also good to know about `any` when you're suppressing implicitAny checks.
>
> So, let's write a utility type `IsAny`, which takes input type `T`. If `T` is `any`, return `true`, otherwise, return `false`.
```typescript
type IsAny = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, true>>,
Expect, false>>,
Expect, false>>,
Expect, false>>,
Expect, false>>,
]
```
其实这个类型在 `@type-challenges/utils` 中就有。
```typescript
type IsAny = (() => A extends T ? 1 : 2) extends (() => A extends any ? 1 : 2) ? true : false
type IsAny2 = Equal
```
### Typed Get
> The [`get` function in lodash](https://lodash.com/docs/4.17.15#get) is a quite convenient helper for accessing nested values in JavaScript. However, when we come to TypeScript, using functions like this will make you lose the type information. With TS 4.1's upcoming [Template Literal Types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#template-literal-types) feature, properly typing `get` becomes possible. Can you implement it?
```typescript
type Get = string
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 'world'>>,
Expect, 6>>,
Expect, { value: 'foobar'; count: 6 }>>,
Expect, never>>,
]
type Data = {
foo: {
bar: {
value: 'foobar'
count: 6
}
included: true
}
hello: 'world'
}
```
简单的字符串操作:
```typescript
type Get = K extends `${infer Key}.${infer R}`
? Key extends keyof T
? Get
: never
: K extends keyof T
? T[K]
: never
```
### String to Number
> Convert a string literal to a number, which behaves like `Number.parseInt`.
```typescript
type ToNumber = string
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, 0>>,
Expect, 5>>,
Expect, 12>>,
Expect, 27>>,
Expect, never>>,
]
```
我的想法是,先获取一个由字符串里面的每一个经过转换后的数字数组:
```typescript
type NumberMap = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type GetResult = S extends `${infer F}${infer R}`
? F extends keyof NumberMap
? GetResult
: never
: Result
```
如果字符串里面出现了无法匹配数字的字符,那么就是 never。
```typescript
type E = [
Expect, [1, 2]>>,
Expect, [0]>>,
Expect, never>>
]
```
然后将得到的数组递归操作再次生成另外一个数组,最后获取它的 Length:
```typescript
type NumberToTuple = Result['length'] extends N
? Result
: NumberToTuple
type GetTenTimes = [
...T, ...T, ...T, ...T, ...T,
...T, ...T, ...T, ...T, ...T
]
type GetLength = T extends [infer F extends number, ...infer R extends number[]]
? GetLength, ...NumberToTuple]>
: Result['length']
```
最后得出结果:
```typescript
type NumberMap = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type GetResult = S extends `${infer F}${infer R}`
? F extends keyof NumberMap
? GetResult
: never
: Result
type NumberToTuple = Result['length'] extends N
? Result
: NumberToTuple
type GetTenTimes = [
...T, ...T, ...T, ...T, ...T,
...T, ...T, ...T, ...T, ...T
]
type GetLength = T extends [infer F extends number, ...infer R extends number[]]
? GetLength, ...NumberToTuple]>
: Result['length']
type ToNumber> = [R] extends [never]
? never
: GetLength
```
然后在社区还有更简单的答案:
```typescript
type ToNumber = S extends `${infer N extends number}` ? N : never;
```
### Tuple Filter
> Implement a type `FilterOut` that filters out items of the given type `F` from the tuple `T`.
```typescript
type FilterOut = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, []>>,
Expect, []>>,
Expect, ['a']>>,
Expect, [1, 'a']>>,
Expect, [1, 'a', false]>>,
Expect, [number | null | undefined]>>,
]
```
逐一排除即可:
```typescript
type FilterOut = T extends [infer J, ...infer R]
? Equal extends true
? FilterOut
: [J] extends [F]
? FilterOut
: FilterOut
: Result
```
### Tuple to Enum Object
> The enum is an original syntax of TypeScript (it does not exist in JavaScript). So it is converted to like the following form as a result of transpilation:
> ```js
> let OperatingSystem;
> (function (OperatingSystem) {
> OperatingSystem[OperatingSystem["MacOS"] = 0] = "MacOS";
> OperatingSystem[OperatingSystem["Windows"] = 1] = "Windows";
> OperatingSystem[OperatingSystem["Linux"] = 2] = "Linux";
> })(OperatingSystem || (OperatingSystem = {}));
> ```
> In this question, the type should convert a given string tuple to an object that behaves like an enum.
> Moreover, the property of an enum is preferably a pascal case.
>
> ```ts
> Enum<["macOS", "Windows", "Linux"]>
> // -> { readonly MacOS: "macOS", readonly Windows: "Windows", readonly Linux: "Linux" }
> ```
> If `true` is given in the second argument, the value should be a number literal.
> ```ts
> Enum<["macOS", "Windows", "Linux"], true>
> // -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
> ```
```typescript
type Enum = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
const OperatingSystem = ['macOS', 'Windows', 'Linux'] as const
const Command = ['echo', 'grep', 'sed', 'awk', 'cut', 'uniq', 'head', 'tail', 'xargs', 'shift'] as const
type cases = [
Expect, {}>>,
Expect,
{
readonly MacOS: 'macOS'
readonly Windows: 'Windows'
readonly Linux: 'Linux'
}
>>,
Expect,
{
readonly MacOS: 0
readonly Windows: 1
readonly Linux: 2
}
>>,
Expect,
{
readonly Echo: 'echo'
readonly Grep: 'grep'
readonly Sed: 'sed'
readonly Awk: 'awk'
readonly Cut: 'cut'
readonly Uniq: 'uniq'
readonly Head: 'head'
readonly Tail: 'tail'
readonly Xargs: 'xargs'
readonly Shift: 'shift'
}
>>,
Expect,
{
readonly Echo: 0
readonly Grep: 1
readonly Sed: 2
readonly Awk: 3
readonly Cut: 4
readonly Uniq: 5
readonly Head: 6
readonly Tail: 7
readonly Xargs: 8
readonly Shift: 9
}
>>,
]
```
这题也是比较容易处理的,只需要根据不同的 N 来决定是使用数组中的值或是索引即可:
```typescript
type PlusOne = Result['length'] extends N
? [...Result, 0]['length']
: PlusOne
type Enum<
T extends readonly string[],
N extends boolean = false,
Result extends Record = {},
Index extends number = 0
> = T extends readonly [infer F extends string, ...infer R extends readonly string[]]
? Enum<
R,
N,
{
readonly [K in F | keyof Result as K extends string ? Capitalize : never]: K extends keyof Result
? Result[K]
: K extends string
? N extends false
? `${K}`
: Index
: never
},
PlusOne
>
: Result
```
### Printf
> Implement `Format` generic.
```typescript
type Format = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect, string>>,
Expect, (s1: string) => string>>,
Expect, (d1: number) => string>>,
Expect, string>>,
Expect, (d1: number) => string>>,
Expect, (d1: number) => (s1: string) => string>>,
]
```
这和之前做的 [C-printf Parser](#C-printf Parser) 类似:
```typescript
type M = {
d: number;
s: string;
}
type Format = T extends `${infer F}${infer R}`
? Prev extends '%'
? F extends '%'
? Format
: F extends keyof M
? (arg: M[F]) => Format
: Format
: Format
: string
```
### Deep object to unique
> TypeScript has structural type system, but sometimes you want a function to accept only some previously well-defined unique objects (as in the nominal type system), and not any objects that have the required fields.
>
> Create a type that takes an object and makes it and all deeply nested objects in it unique, while preserving the string and numeric keys of all objects, and the values of all properties on these keys.
>
> The original type and the resulting unique type must be mutually assignable, but not identical.
```typescript
type DeepObjectToUniq = any
/* _____________ Test Cases _____________ */
import type { Equal, IsFalse, IsTrue } from '@type-challenges/utils'
type Quz = { quz: 4 }
type Foo = { foo: 2; baz: Quz; bar: Quz }
type Bar = { foo: 2; baz: Quz; bar: Quz & { quzz?: 0 } }
type UniqQuz = DeepObjectToUniq
type UniqFoo = DeepObjectToUniq
type UniqBar = DeepObjectToUniq
declare let foo: Foo
declare let uniqFoo: UniqFoo
uniqFoo = foo
foo = uniqFoo
type cases = [
IsFalse>,
IsFalse>,
IsTrue>,
IsTrue>,
IsFalse>,
IsFalse>,
IsFalse>,
IsTrue>,
IsTrue