Skip to content

Commit 43e8701

Browse files
✨ add medium topic and fix some style
1 parent 8298c07 commit 43e8701

32 files changed

+1827
-2172
lines changed

docs/.vitepress/config.mts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,30 @@ export default defineConfig({
3232
{ text: '实现Parameters', link: '/challenges/easy/paramEters' },
3333
]
3434
},
35-
{ text: '中等', link: '/challenges/medium/' },
35+
{ text: '中等',
36+
items: [
37+
{ text: '实现Absolute', link: '/challenges/medium/Absolute' },
38+
{ text: '实现Capitalize', link: '/challenges/medium/Capitalize' },
39+
{ text: '百分比解析器', link: '/challenges/medium/percentageParser' },
40+
{ text: '获取函数返回类型', link: '/challenges/medium/function-return' },
41+
{ text: '计算字符的长度', link: '/challenges/medium/character-length' },
42+
{ text: '实现DeepReadonly', link: '/challenges/medium/DeepReadonly' },
43+
{ text: '实现ReplaceAll', link: '/challenges/medium/ReplaceAll' },
44+
{ text: '实现TrimLeft', link: '/challenges/medium/TrimLeft' },
45+
{ text: '实现TrimRight', link: '/challenges/medium/TrimRight' },
46+
{ text: '实现Trim', link: '/challenges/medium/Trim' },
47+
{ text: '实现Diff', link: '/challenges/medium/Diff' },
48+
{ text: '实现Omit', link: '/challenges/medium/Omit' },
49+
{ text: '实现ReadonlyKeys', link: '/challenges/medium/ReadonlyKeys' },
50+
{ text: '实现Flatten', link: '/challenges/medium/Flatten' },
51+
{ text: '实现Merge', link: '/challenges/medium/Merge' },
52+
{ text: '实现Promise.all', link: '/challenges/medium/Promise.all' },
53+
{ text: '实现Replace', link: '/challenges/medium/Replace' },
54+
{ text: '实现KebabCase', link: '/challenges/medium/KebabCase' },
55+
{ text: '实现Pop', link: '/challenges/medium/Pop' },
56+
{ text: '最后一个元素', link: '/challenges/medium/lastOne' },
57+
]
58+
},
3659
{ text: '困难', link: '/challenges/hard/' },
3760
{ text: '地狱', link: '/challenges/extreme/' }
3861
]

docs/challenges/medium/Absolute.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: 实现Absolute
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现一个接收 string,number 或 bigInt 类型参数的`Absolute`类型,返回一个正数字符串。
10+
11+
例如
12+
13+
```ts
14+
type Test = -100;
15+
type Result = Absolute<Test>; // expected to be "100"
16+
```
17+
18+
## 分析
19+
20+
这个题本质也是字符的推断匹配,但是由于入参是 number 类型的,所以在进行推断匹配前需要进行一次转化,对于 ts 来讲,也非常简单:
21+
22+
```ts
23+
type NumberToString<T extends number> = `${T}`;
24+
25+
// Case1 = '100'
26+
type Case1 = NumberToString<100>;
27+
```
28+
29+
转换成字符后,可以直接进行 `-` 号的匹配,有 `-` 号,就只保留剩余的字符,否则全部保留即可。
30+
31+
## 题解
32+
33+
```ts
34+
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer S}`
35+
? // 有 - 号,保留剩余的字符
36+
`${S}`
37+
: // 否则保留原字符即可
38+
`${T}`;
39+
```
40+
41+
## 知识点
42+
43+
1. number 转 string
44+
2. 字符推断匹配套路: `` A extends `-${infer R}` ``

docs/challenges/medium/AnyOf.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: AnyOf
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
在类型系统中实现类似于 Python 中 `any` 函数。类型接收一个数组,如果数组中任一个元素为真,则返回 `true`,否则返回 `false`。如果数组为空,返回 `false`
10+
11+
例如:
12+
13+
```ts
14+
type Sample1 = AnyOf<[1, '', false, [], {}]>; // expected to be true.
15+
type Sample2 = AnyOf<[0, '', false, [], {}]>; // expected to be false.
16+
```
17+
18+
## 分析
19+
20+
这个题目看起来只需要遍历一次元组,遇到 false 元素,就继续递归判断剩余元素,否则就返回 true,直到遍历结束,那么就返回 false。
21+
22+
所以问题就转换问,怎么判断一个元素为 false,从这道题目的 case 中推断,可以认为:
23+
24+
`0 | false | '' | [] | undefined | null | {}` 是 false。
25+
26+
可以通过定义一个 Zerolist 的类型,`A extends Zerolist` 就简单认为这个元素是 false。
27+
28+
## 题解
29+
30+
```ts
31+
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
32+
T,
33+
>() => T extends Y ? 1 : 2
34+
? true
35+
: false;
36+
37+
type Zerolist = 0 | false | '' | [] | undefined | null;
38+
39+
type AnyOf<T extends readonly any[]> = T extends [infer F, ...infer R]
40+
? F extends Zerolist
41+
? AnyOf<R>
42+
: // 单独处理 {} 的判定
43+
Equal<F, {}> extends true
44+
? AnyOf<R>
45+
: true
46+
: false;
47+
```
48+
49+
## 知识点
50+
51+
1. `[] | Function | { a: any } extends {}` 为 true
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
title: AppendToObject
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现一个为接口添加一个新字段的类型。该类型接收三个参数,返回带有新字段的接口类型。
10+
11+
例如:
12+
13+
```ts
14+
type Test = { id: '1' };
15+
type Result = AppendToObject<Test, 'value', 4>; // expected to be { id: '1', value: 4 }
16+
```
17+
18+
## 分析
19+
20+
这题操作的类型时对象,在对象上新增属性,相比较之前的 [实现 Omit](/medium/实现Omit.md) 要简单很多,只需要根据传入的参数生成新的类型,同当前类型交叉即可得到结果。
21+
22+
## 题解
23+
24+
```ts
25+
type Merge<T> = {
26+
[P in keyof T]: T[P];
27+
};
28+
29+
type AppendToObject<T, U extends string, V> = Merge<
30+
T & {
31+
[K in U]: V;
32+
}
33+
>;
34+
```
35+
36+
核心在于 `T & { [L in U]: V }`,其实到这里应该有同学提出疑惑了,为什么需要 `Merge` 包裹一层?
37+
38+
原因具体不清楚,但是可以直接从表现上来看:
39+
40+
```ts
41+
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
42+
T,
43+
>() => T extends Y ? 1 : 2
44+
? true
45+
: false;
46+
47+
type A = {
48+
a: number;
49+
b: number;
50+
};
51+
52+
type B = {
53+
a: number;
54+
} & {
55+
b: number;
56+
};
57+
58+
type Merge<T> = {
59+
[P in keyof T]: T[P];
60+
};
61+
62+
// false
63+
type Case1 = Equal<A, B> extends true ? true : false;
64+
65+
// true
66+
type Case2 = Equal<A, Merge<B>> extends true ? true : false;
67+
```
68+
69+
## 知识点
70+
71+
1. 对象交叉
72+
2. 交叉后的对象 Merge
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
title: 实现Capitalize
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现 `Capitalize<T>` 它将字符串的第一个字母转换为大写,其余字母保持原样。
10+
11+
例如
12+
13+
```ts
14+
type capitalized = Capitalize<'hello world'>; // expected to be 'Hello world'
15+
```
16+
17+
## 分析
18+
19+
思路其实比较简单,就是找到第一个字符,大写之后和其余字符拼接即可。
20+
21+
这里值得一提的是 ts 本身自带这个方法,可以参考 官方文档 `uppercasestringtype`,但是其定义的地方是 intrinsic,我们看不到。
22+
23+
同时 Ts 还自带了大写整个字符的方法 Uppercase, 想要实现本题目,可以通过推断匹配的方式选出第一个字符后,大写该字符,并和原字符拼接即可。
24+
25+
## 题解
26+
27+
```ts
28+
type MyCapitalize<S extends string> = S extends `${infer F}${infer R}`
29+
? `${Uppercase<F>}${R}`
30+
: '';
31+
```
32+
33+
## 知识点
34+
35+
1. 字符串推断匹配: `` A extends `${infer F}${infer R}` ``
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
title: 实现DeepReadonly
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
实现一个通用的`DeepReadonly<T>`,它将对象的每个参数及其子对象递归地设为只读。
10+
11+
您可以假设在此挑战中我们仅处理对象。数组,函数,类等都无需考虑。但是,您仍然可以通过覆盖尽可能多的不同案例来挑战自己。
12+
13+
例如
14+
15+
```ts
16+
type X = {
17+
x: {
18+
a: 1;
19+
b: 'hi';
20+
};
21+
y: 'hey';
22+
};
23+
24+
type Expected = {
25+
readonly x: {
26+
readonly a: 1;
27+
readonly b: 'hi';
28+
};
29+
readonly y: 'hey';
30+
};
31+
32+
type Todo = DeepReadonly<X>; // should be same as `Expected`
33+
```
34+
35+
## 分析
36+
37+
```ts
38+
type DeepReadonly<T> = {
39+
readonly [P in keyof T]: T[P] extends {} ? DeepReadonly<T[P]> : T[P];
40+
};
41+
```
42+
43+
通过 `T[P] extends {} ? DeepReadonly<T[P]> : T[P]` 对属性值进行二次判断,如果是继承自 对象,那么就递归处理,否则返回原始属性值。
44+
45+
理论上这样就能够结束本题目,但是还存在两个特殊场景,元组和函数,都继承自对象 `{}`
46+
47+
```ts
48+
type Case1 = [] extends {} ? true : false; // true
49+
type Case2 = (() => {}) extends {} ? true : false; // true
50+
```
51+
52+
对于元组,之前其实也比较隐晦的提到过,元组的遍历,可以通过和对象一摸一样的遍历手段进行:
53+
54+
```ts
55+
type Traverse<T> = {
56+
[P in keyof T]: T[P];
57+
};
58+
59+
type Case3 = Traverse<[1, 2, 3]>; // [1, 2, 3]
60+
```
61+
62+
元组的遍历就是这样,同对象一样,也可以增加 `readonly` 等修饰符。故这一场景可以忽略。
63+
64+
但是对于函数来讲,就出现了错误:
65+
66+
```ts
67+
type DeepReadonly<T> = {
68+
readonly [P in keyof T]: T[P] extends {} ? DeepReadonly<T[P]> : T[P];
69+
};
70+
71+
// Case4 = {}
72+
type Case4 = DeepReadonly<() => {}>;
73+
74+
// Case5 = never
75+
type Case5 = keyof (() => {});
76+
```
77+
78+
对于函数执行遍历,那么由于 `keyof (() => {})``never`,新的类型的属性就为空,从而返回 `{}`
79+
80+
所以对于此题,只需要增加函数的额外处理即可。
81+
82+
## 题解
83+
84+
```ts
85+
type DeepReadonly<T> = {
86+
readonly [P in keyof T]: T[P] extends Function
87+
? T[P]
88+
: T[P] extends {}
89+
? DeepReadonly<T[P]>
90+
: T[P];
91+
};
92+
```
93+
94+
增加函数的场景判断即可。
95+
96+
## 知识点
97+
98+
1. 递归处理嵌套问题
99+
2. 元组可以使用遍历对象的方法进行遍历
100+
3. `(() => {}) extends {}` 结果为 true

docs/challenges/medium/Diff.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: 实现Diff
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
## 题目描述
8+
9+
获取两个接口类型中的差值属性。
10+
11+
```ts
12+
type Foo = {
13+
a: string;
14+
b: number;
15+
};
16+
type Bar = {
17+
a: string;
18+
c: boolean;
19+
};
20+
21+
type Result1 = Diff<Foo, Bar>; // { b: number, c: boolean }
22+
type Result2 = Diff<Bar, Foo>; // { b: number, c: boolean }
23+
```
24+
25+
## 分析
26+
27+
这道题目其实就比较宽泛了,获取只存在于 A 或 只存在于 B 中的属性,网上有很多借助 `Omit`, `Exclude`, `&` 的解法,我认为都不太直观,其实借助 `as` 非常好实现。关于 `as` 可以参考 [实现 Omit](/medium/实现Omit.md) 中介绍的部分。
28+
29+
首先通过 `keyof A | keyof B` 可以获取所有的属性,接下来只需要让即存在于 A 中的属性 又存在于 B 中的属性为 never 即可去除该属性,也就是 `P extends keyof A & keyof B ? never : P`。讲到这里题解基本就出来了。
30+
31+
## 题解
32+
33+
```ts
34+
type Diff<O, O1> = {
35+
// keyof O | keyof O1 拿到所有的属性中,通过 keyof O & keyof O1 判断是否是公共属性,如是公共属性,置为 never
36+
[P in keyof O | keyof O1 as P extends keyof O & keyof O1
37+
? never
38+
: P]: P extends keyof O ? O[P] : P extends keyof O1 ? O1[P] : never; // 补充属性值即可
39+
};
40+
```
41+
42+
## 知识点
43+
44+
1.[实现 Omit](/medium/实现Omit.md)

0 commit comments

Comments
 (0)