TODO
TODO
题目
TODO
解题思路
这道题要求在类型层面构造帕斯卡三角形(杨辉三角)。核心思路与手动构造杨辉三角完全一致:每一行的每个元素等于上一行相邻两个元素之和。
解法分为以下几个层次:
数字转元组 (
FromLength):TypeScript 类型系统没有原生算术运算,因此用1的元组来表示数字。FromLength<3>生成[1, 1, 1],这是所有类型层面"数学运算"的基础。类型层面加法 (
Sum):将两个数字分别转为元组,展开合并后读取length,即可得到两数之和。逐元素求和 (
SumArr):对两个等长数组逐元素求和。使用递归的头尾解构模式——取出各自的首元素求和,追加到累加器,然后对尾部递归。生成下一行 (
GetNextRow):这里的巧妙之处在于:给定一行[1, 3, 3, 1],分别在前后补零得到[0, 1, 3, 3, 1]和[1, 3, 3, 1, 0],然后逐元素求和得到[1, 4, 6, 4, 1]。这个操作完美地实现了杨辉三角的生成规则。构建完整三角 (
PascalArr):从[[1]]开始,反复将最后一行传入GetNextRow生成新行并追加。用元组长度作为计数器,每次递归缩减一个元素,长度为 1 时停止。入口类型 (
Pascal):将数字N转为元组后交给PascalArr处理。
深度解析
基于元组长度的算术运算
TypeScript 类型系统不支持数值计算,但元组类型提供了一条通道。元组的 ["length"] 属性是数字字面量类型,而展开运算符 [...A, ...B] 可以拼接元组。两者结合就能编码加法:[...FromLength<A>, ...FromLength<B>]["length"] 计算 A + B。这个模式是几乎所有类型层面数学题的基石。
递归条件类型与 infer 的组合
SumArr 展示了一个强大的模式:递归结构解构。条件类型 [A, B] extends [[infer HeadA, ...infer TailA], [infer HeadB, ...infer TailB]] 在单次检查中同时解构两个数组,比分别检查更健壮——在真值分支中可以保证两个数组都非空。
infer ... extends number[] 语法(TypeScript 4.7 引入)在推断时直接收窄类型,避免了额外的条件检查。
GetLast 的索引技巧
GetLast<T> 使用了一个精妙的索引技巧:[never, ...T][T["length"]]。如果 T = [a, b, c](长度为 3),那么 [never, ...T] 就是 [never, a, b, c],在位置 3 处索引得到 c。用一次索引访问取代了完整的递归遍历。
补零滑窗技巧
GetNextRow<[1, 3, 3, 1]> 计算 SumArr<[0, 1, 3, 3, 1], [1, 3, 3, 1, 0]>。通过将同一行分别在首尾补零并错开,逐元素求和自然产生下一行。这与命令式实现中的"滑窗"技巧异曲同工,被优雅地移植到了纯声明式的类型层面。
元组缩减实现计数递减
类型层面没有 N - 1 运算,解法将 N 转为元组,每次迭代用 N extends [1, ...infer Tail] 剥离一个元素。当元组长度为 1 时递归终止。这是类型层面实现倒计数循环的标准模式。
总结
- 元组长度是 TypeScript 类型系统中通用的算术原语。将数字转为元组、用展开运算符操作、再读取
.length,是实现加法的经典方法。 - 递归条件类型支持同时解构多个数组(
[A, B] extends [[infer ...], [infer ...]]),可以在单次递归中并行遍历多个结构。 - 巧妙的索引访问(如
[never, ...T][T["length"]])可以用单个表达式替代整个递归工具类型——始终寻找基于索引的捷径。 - 补零技巧(
[0, ...row]和[...row, 0])将"相邻元素求和"这一复杂操作转化为简单的逐元素求和,展示了正确的数据变换能极大简化类型层面的逻辑。 - 类型层面的循环通过元组缩减而非数值递减来驱动——将计数器转为元组,逐个剥离元素,检查长度以终止递归。
