TS 类型体操笔记 - 476 Sum

88 阅读3分钟

概述

类型体操 是关于 TS 类型编程的一系列挑战(编程题目)。通过这些挑战,我们可以加深对 TS 类型系统的理解,举一反三,在实际工作中解决类似问题。

本文是我对 476 Sum 的解题笔记。

本文的读者是正在做 TS 类型体操,对上述题目感兴趣,希望获得更多解读的同学。读者需要具备一定的 TS 基础知识,这部分推荐阅读另一篇优秀博文 Typescript 类型编程,从入门到念头通达😇

题目和答案

先直接贴出题目与答案,呈现问题全貌。

题目

Implement a type Sum<A, B> that summing two non-negative integers and returns the sum as a string. Numbers can be specified as a string, number, or bigint.

For example,

type T0 = Sum<2, 3> // '5'
type T1 = Sum<'13', '21'> // '34'
type T2 = Sum<'328', 7> // '335'
type T3 = Sum<1_000_000_000_000n, '123'> // '1000000000123'

答案

Issue 22195

// reverse a string
type ReverseString<S extends string> = S extends `${infer F}${infer Rest}` ? `${ReverseString<Rest>}${F}` : ''

// convert number to tuple, tail-recursion
type Number2Tuple<N extends number, _T extends unknown[] = []> = _T['length'] extends N ? _T : Number2Tuple<N, [..._T, unknown]>

// sum two numbers
type SumNumber<A extends number, B extends number> = [...Number2Tuple<A>, ...Number2Tuple<B>]['length'] & number

// sum two numbers in reversed string format
type SumReversedString<A extends string, B extends string> = 
  A extends `${infer AF extends number}${infer ARest}`
  ? B extends `${infer BF extends number}${infer BRest}`
    ? ReverseString<`${SumNumber<AF, BF>}`> extends `${infer Gewei}${infer Shiwei}`
      ? `${Gewei}${SumReversedString<ARest, SumReversedString<BRest, Shiwei>>}`
      : never
    : A
  : B

type Sum<A extends string | number | bigint, B extends string | number | bigint> = ReverseString<SumReversedString<ReverseString<`${A}`>, ReverseString<`${B}`>>>

解题思路

问题分析

在 ts 类型系统中没有数学运算,所以普通的加法可以通过 tuple 计数的方式实现,但这样的方式仅适用于较小的数字计算。本题题面上的挑战点包括

  1. 支持大数计算
  2. 支持多种输入格式(number, bigint, string)

由于要支持大数计算,算法不能直接依赖 tuple 计数,可考虑基于数串模拟十进制加法过程,单独的每一位数字相加时再使用 tuple 计数来实现。

如何支持多种输入格式呢?通过 template literal 处理一下输入,就能将它们标准化为统一格式的字符串。

算法设计

回顾一下基于数串的加法过程

  1. 两个数字从个位开始对齐
  2. 从低位到高位开始处理,直到处理完每一位数字
  3. 每一位上两个数字相加,如果结果是一位数字,就直接作为这一位的结果;如果结果是两位数字,则取个位为这一位的结果,而十位(只可能是 1)则进位到下一位,和下一位的数字们一起相加

对于十进制的数字字符串来说,要模拟『右对齐』,并『从右往左』进行处理,有两种方案

  1. 将字符串转为 tuple,这样可以通过模式匹配提取最后一个元素
  2. 将字符串进行翻转,这样提取的字符串首位就是原始字符串的最后一位

这里选择基于字符串翻转的方案。

至于单独的每一位上的两个数字相加,使用基于 tuple 的加法计算就可以了,因为数字范畴有限,所以不会遇到规模上的问题。当然也可以做穷举映射,但是那样代码量会多很多,权衡之下无必要。

每一位上数字相加后如何确定有没有进位呢?只需要将相加的结果进行一下解析,把个位和十位分别提取出来即可。这一点通过字符串的模式匹配也能实现。如果有进位,在下一位的计算时再加 1 即可。

代码

有了上面的问题分析和算法设计,代码读起来应该比较容易,能自解释。

注意 SumReversedString 并没有做尾递归处理。因为它面对的问题规模是相加数字的位数,在这个挑战下普通递归的 50 限制已经绰绰有余了,如果要支持更长的数字位数,将这个函数处理为尾递归即可。