TypeScript 类型体操问题解析

yuan 1年前 ⋅ 146 阅读
ad

 

众所周知,TypeScript 拥有一个图灵完备的类型系统

题目

实现类型级整数比较器。我们提供了一个枚举来表示比较结果,如下所示:

  • 如果 a 大于 b ,类型应为 Comparison.Greater。

  • 如果 a 和 b 相等,则类型应为 Comparison.Equal 。

  • 如果 a 低于 b ,则类型应为 Comparison.Lower 。

注意, 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>>,
];

思路

  1. 审题和看测试用例,总结题目内容为比较两个整数的大小,可能为0,为负数。

  2. 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种:

  1. 两个数都是正数

  2. 第一个为负数,第二个为正数

  3. 第一个为正数, 第二个为负数

  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>>,
];

关于纵目

江苏纵目信息科技有限公司是一家专注于运维监控软件产品研发与销售的高科技企业。覆盖全链路应用性能监控、IT基础设施监控、物联网数据采集数据观测等场景,基于Skywalking、Zabbix、ThingsBoard等开源体系构建了ArgusAPM、ArgusOMS、ZeusIoT等产品,致力于帮助各行业客户构建集聚可观测性的统一运维平台、物联网大数据平台。

  点赞 1   收藏 1
  • yuan
    共发布32篇文章 获得1个收藏
全部评论: 0