众所周知,TypeScript 拥有一个
题目
实现类型级整数比较器。我们提供了一个枚举来表示比较结果,如下所示:
-
如果 a 大于 b ,类型应为 Comparison.Greater。
-
如果 a 和 b 相等,则类型应为 Comparison.Equal 。
-
注意, a 和 b 可以是正整数或负整数或零,甚至一个为正而另一个为负。
测试用例和工具函数
type Expect<T extends true> = T;
// 关于Equal的实现,可以翻阅[这个issues](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650)
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
enum Comparison {
Greater,
Equal,
Lower,
}
type cases = [
Expect<Equal<Comparator<5, 5>, Comparison.Equal>>,
Expect<Equal<Comparator<5, 6>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 8>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 0>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, 0>, Comparison.Lower>>,
Expect<Equal<Comparator<0, 0>, Comparison.Equal>>,
Expect<Equal<Comparator<0, -5>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -3>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -3>, Comparison.Lower>>,
Expect<Equal<Comparator<-25, -30>, Comparison.Greater>>,
Expect<Equal<Comparator<15, -23>, Comparison.Greater>>,
Expect<Equal<Comparator<-36, 36>, Comparison.Lower>>,
Expect<Equal<Comparator<27, 27>, Comparison.Equal>>,
Expect<Equal<Comparator<-38, -38>, Comparison.Equal>>,
Expect<Equal<Comparator<1, 100>, Comparison.Lower>>,
Expect<Equal<Comparator<-100, 1>, Comparison.Lower>>,
Expect<Equal<Comparator<1, -100>, Comparison.Greater>>,
Expect<Equal<Comparator<-100, -1>, Comparison.Lower>>,
Expect<Equal<Comparator<-1, -100>, Comparison.Greater>>,
];
思路
-
审题和看测试用例,总结题目内容为比较两个整数的大小,可能为0,为负数。
-
TypeScript类型系统中,是没有比较两个数大小的。所以需要实现比较数字大小的类型;可以先实现正整数的比较,再拓展负数的比较。
分解思路
前置知识
首先,使用下面的代码可以生成一个长度为 N 的类型数组:
type MarkArr<N extends number, R extends unknown[] = []> =
R['length'] extends N
? R
: MarkArr<N, [0, R]>;
然后,通过解构数组并获取长度,可以实现类型的相加:
type Plus<A, B> = [MarkArr<A>, MarkArr<B>]["length"];
有了这些前置知识,就可以着手解决整数比较的问题了。
解决正整数比较
type CompareNumber<A extends number, B extends number> = keyof MarkArr<A> extends keyof MarkArr<B>
? Comparison.Lower
: Comparison.Greater;
上述代码中的 MarkArr<A>
和 MarkArr<B>
分别生成了 A 和 B 对应长度的数组,然后通过比较数组的键的数量来判断大小关系。
解决相等判断
type Comparator<A extends number, B extends number> = Equal<A, B> extends true
? Comparison.Equal
: CompareNumber<A, B>;
在这里,通过 Equal
判断 A 和 B 是否相等,如果相等则返回 Comparison.Equal
,否则进行正整数比较。
解决负数比较
可分解为下面4种:
-
两个数都是正数
-
第一个为负数,第二个为正数
-
第一个为正数, 第二个为负数
-
两个都为负数
首先可以用模板字符串解决四种情况的判断
type P = `${M}${N}` extends `${infer M1}${infer N1}`;
type N = `${M}${N}` extends `-${infer M2}-${infer N2}`;
type PN = `${M}${N}` extends `${infer M1}-${infer N1}`;
type NP= `${M}${N}` extends `-${infer M1}${infer N1}`;
// 模板字符串infer之后还是字符串,所以要先把字符转为数字
type String2Number<S extends string, T extends any[] = []> = `${S}` extends `${T['length']}`
? T['length']
: String2Number<S, [0, T]>;
type CompareValue<
M extends number,
N extends number,
> =
// 都是负数
`${M}${N}` extends `-${infer M1}-${infer N1}`
? CompareNegative<String2Number<M1>, String2Number<N1>>
// 第一个负数
: `${M}${N}` extends `-${infer M1}${infer N1}`
? Comparison.Lower
// 第二个负数
: `${M}${N}` extends `${infer M1}-${infer N1}`
? Comparison.Greater
// 都是正数
: `${M}${N}` extends `${infer M2}${infer N2}`
? CompareNumber<String2Number<M2>, String2Number<N2>>
: never;
上述代码通过模板字符串判断四种情况,然后分别进行处理,最终得到负数的比较结果。
完整代码(包含测试用例)
type Expect<T extends true> = T;
// 关于 Equals 的实现,可以查看[这个 issues](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650)
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
enum Comparison {
Greater,
Equal,
Lower,
}
type String2Number<S extends string, T extends any[] = []> = `${S}` extends `${T['length']}`
? T['length']
: String2Number<S, [0, T]>;
type MarkArr<N extends number, R extends unknown[] = []> =
R['length'] extends N
? R
: MarkArr<N, [0, R]>;
type CompareNumber<A extends number, B extends number>
= keyof MarkArr<A> extends keyof MarkArr<B>
? Comparison.Lower
: Comparison.Greater;
type CompareNegative<A extends number, B extends number> = keyof MarkArr<A> extends keyof MarkArr<B>
? Comparison.Greater
: Comparison.Lower;
type CompareValue<
M extends number,
N extends number,
> = `${M}${N}` extends `-${infer M1}-${infer N1}`
? CompareNegative<String2Number<M1>, String2Number<N1>>
: `${M}${N}` extends `-${infer M1}${infer N1}`
? Comparison.Lower
: `${M}${N}` extends `${infer M1}-${infer N1}`
? Comparison.Greater
: `${M}${N}` extends `${infer M2}${infer N2}`
? CompareNumber<String2Number<M2>, String2Number<N2>>
: never;
type Comparator<A extends number, B extends number> = Equal<A, B> extends true
? Comparison.Equal
: CompareValue<A, B>;
type cases = [
Expect<Equal<Comparator<5, 5>, Comparison.Equal>>,
Expect<Equal<Comparator<5, 6>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 8>, Comparison.Lower>>,
Expect<Equal<Comparator<5, 0>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, 0>, Comparison.Lower>>,
Expect<Equal<Comparator<0, 0>, Comparison.Equal>>,
Expect<Equal<Comparator<0, -5>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -3>, Comparison.Greater>>,
Expect<Equal<Comparator<5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -7>, Comparison.Greater>>,
Expect<Equal<Comparator<-5, -3>, Comparison.Lower>>,
Expect<Equal<Comparator<-25, -30>, Comparison.Greater>>,
Expect<Equal<Comparator<15, -23>, Comparison.Greater>>,
Expect<Equal<Comparator<-36, 36>, Comparison.Lower>>,
Expect<Equal<Comparator<27, 27>, Comparison.Equal>>,
Expect<Equal<Comparator<-38, -38>, Comparison.Equal>>,
Expect<Equal<Comparator<1, 100>, Comparison.Lower>>,
Expect<Equal<Comparator<-100, 1>, Comparison.Lower>>,
Expect<Equal<Comparator<1, -100>, Comparison.Greater>>,
Expect<Equal<Comparator<-100, -1>, Comparison.Lower>>,
Expect<Equal<Comparator<-1, -100>, Comparison.Greater>>,
];
未完待续
上面的代码还有一些局限性,主要是在处理极大整数时可能存在问题。以下是更多的测试用例,感兴趣的同学可以修改代码并通过这些测试用例:
type cases = [
Expect<Equal<Comparator<9007199254740992, 9007199254740992>, Comparison.Equal>>,
Expect<Equal<Comparator<-9007199254740992, -9007199254740992>, Comparison.Equal>>,
Expect<Equal<Comparator<9007199254740991, 9007199254740992>, Comparison.Lower>>,
Expect<Equal<Comparator<9007199254740992, 9007199254740991>, Comparison.Greater>>,
Expect<Equal<Comparator<-9007199254740992, -9007199254740991>, Comparison.Lower>>,
Expect<Equal<Comparator<-9007199254740991, -9007199254740992>, Comparison.Greater>>,
];