awesome-typescript icon indicating copy to clipboard operation
awesome-typescript copied to clipboard

「重学TS 2.0 」TS 练习题第三十三题

Open semlinker opened this issue 3 years ago • 5 comments

实现一个 ToNumber 工具类型,用于实现把数值字符串类型转换为数值类型。具体的使用示例如下所示:

type ToNumber<T extends string> = // 你的实现代码

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

请在下面评论你的答案

semlinker avatar Sep 22 '21 13:09 semlinker

首先利用 TS 模板字符串把默认数组 S 的长度转为字符串,之后那这个字符串和 T 比较,若相等,表示当前 S 的长度和传入的字符串所对应的数值相等 (如,"15" --> 15),否则增加 S 的长度,进行下一次 ToNumber 判断

不过这种方式可能会出现一个警告:Type instantiation is excessively deep and possibly infinite. ts(2589),因为字符串过大时,可能会导致递归次数变多,个人认为可以忽略此错误

基本上,这种和数组有关系的类型,都需要构建一个辅助数组来进行判断

type ToNumber<T extends string, S extends any[] = [], L extends number = S['length']> = 
  `${L}` extends T ? L : ToNumber<T, [...S, 1]>

//  用例
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

xiaoYuanDun avatar Sep 23 '21 02:09 xiaoYuanDun

一个新的思路,感觉有点复杂了,不过比之前的最大限制提升了一些

// 10进制数,递归数不超过10,解决了递归次数过多的报错问题
type GetNumber<T extends any, S extends any[] = [], L extends number = S['length']> = 
  `${L}` extends T ? L : GetNumber<T, [...S, 1]>

// 字符串转反向数组, 方便从后向前构建目标数组, 如: '1234' -> ['4', '3', '2', '1']
type StrToReverseArr<T extends string> = 
  T extends `${infer R1}${infer R2}` ? R2 extends '' ? [R1] : [...StrToReverseArr<R2>, R1] : []

/**
 * 辅助矩阵, 用于构建当前位数的值, 那上面的 ['4', '3', '2', '1'] 来说, 相当于:
 * 
 * 个位数: 4 --> 构建一个长度为 4*0001 的数组, Matrix<[1], 4>
 * 十位数: 3 --> 构建一个长度为 3*0010 的数组, Matrix<Matrix<[1], 10>, 3> 
 * 百位数: 2 --> 构建一个长度为 2*0100 的数组, Matrix<Matrix<Matrix<[1], 10>, 10>, 2> 
 * 千位数: 1 --> 构建一个长度为 1*1000 的数组, Matrix<Matrix<Matrix<Matrix<[1], 10>, 10>, 10> , 1> 
 */
type Matrix<T extends any[], I extends number> = [
    [],
    [...T],
    [...T, ...T],
    [...T, ...T, ...T],
    [...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
    [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
][I]

// 生成并合并所有位数的数组
type GetCurArr<
    T extends string,                       //  初始参数, 只在第一次执行时生效, 后面用空字符串 '' 占位
    A extends any[] = StrToReverseArr<T>,   //  反转后的数组, 只在第一次时通过计算得到, 后面都是递归传入
    D extends any[] = [1],                  //  当前位数的单位长度, 核心是通过这个构建每位所对应的数组, 然后合并他们
> = A extends [infer R1, ...infer R2] 
  ? [...GetCurArr<'', R2, Matrix<D, 10>>, ...Matrix<D, GetNumber<R1>>] 
  : []

type ToNumber<T extends string> = GetCurArr<T>['length']

//  用例
type T0 = ToNumber<"123">; // 123
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"999">; // 999

// type T3 = GetCurArr<"1999">; 
// 还是没有完全解决递归的问题, 超过 1000 会报 "Type produces a tuple type that is too large to represent.(2799)"

xiaoYuanDun avatar Sep 23 '21 06:09 xiaoYuanDun

type ToNumber<T extends string, A extends any[] = []> = `${A["length"]}` extends T
  ? A["length"]
  : ToNumber<T, [...A, '']>;

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

思路: ts里运算很匮乏,并没有直接的数字运算,这里巧妙的利用了数组长度来实现,通过递归构造数组,使得构造出来的数组长度和期望的匹配,主要要把数组长度通过字符串模板的方式转换成字符串。否则永远匹配不成功

zhaoxiongfei avatar Oct 03 '21 05:10 zhaoxiongfei

type ToNumber<
  T extends string,
  A extends any[] = []
> = `${A['length']}` extends T ? A['length'] : ToNumber<T, [...A, T]> 

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

zhengyimeng avatar Nov 14 '22 03:11 zhengyimeng

// 这个O就算是个数字也可以扩展才对,这就是最佳解法
type ToNumber<T extends string> = T extends `${infer O extends number}` ? O : never

type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20

zhengyimeng avatar Nov 18 '22 02:11 zhengyimeng