ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

type-challenges 进阶实战:用模板字面量类型实现 Integers Comparator(Extreme 难度全解析)

type-challenges 进阶实战:用模板字面量类型实现 Integers Comparator(Extreme 难度全解析) 示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载导读本篇文章围绕 type-challenges 仓库中的 Extreme 难度题目Integers Comparator00274展开目标是在类型层面实现一个整数比较器ComparatorA, B它接收两个数字类型A与B可为正整数、负整数或零甚至一方为正另一方为负并返回枚举Comparison.Greater / Comparison.Equal / Comparison.Lower中对应的一项。读完本文你将掌握模板字面量类型template literal types的解析手法、数字字符串与符号的处理技巧、类型级递归的进位/借位算法以及如何用type-challenges/utils中的Equal/Expect组合来验证复杂类型运算结果。一、题目是什么Integers Comparator 的完整需求本题由 Pig FangGitHub 用户 g-plane难度标记为extreme标签为template-literal与math见 info.yml。题目给定了结果枚举enum Comparison { Greater, Equal, Lower, }并要求实现type ComparatorA extends number, B extends number any比较规则若a大于b结果为Comparison.Greater若a与b相等结果为Comparison.Equal若a小于b结果为Comparison.Lower。题目特别强调a与b可以是正整数、负整数或零甚至可以一个为正、一个为负。也就是说比较逻辑必须涵盖四种符号组合正正、负负、正负、负正以及零的边界情况这比单纯的“同号比较”要复杂得多。配套测试官方给出的一手验证仓库在 test-cases.ts 中给出了可运行的正向测试用例可以作为实现的验收标准import type { Equal, Expect } from type-challenges/utils type cases [ ExpectEqualComparator5, 5, Comparison.Equal, ExpectEqualComparator5, 6, Comparison.Lower, ExpectEqualComparator5, 8, Comparison.Lower, ExpectEqualComparator5, 0, Comparison.Greater, ExpectEqualComparator-5, 0, Comparison.Lower, ExpectEqualComparator0, 0, Comparison.Equal, ExpectEqualComparator0, -5, Comparison.Greater, ExpectEqualComparator5, -3, Comparison.Greater, ExpectEqualComparator5, -7, Comparison.Greater, ExpectEqualComparator-5, -7, Comparison.Greater, ExpectEqualComparator-5, -3, Comparison.Lower, ExpectEqualComparator-25, -30, Comparison.Greater, ExpectEqualComparator15, -23, Comparison.Greater, ExpectEqualComparator40, 37, Comparison.Greater, ExpectEqualComparator-36, 36, Comparison.Lower, ExpectEqualComparator27, 27, Comparison.Equal, ExpectEqualComparator-38, -38, Comparison.Equal, // 100 数量级 ExpectEqualComparator1, 100, Comparison.Lower, ExpectEqualComparator100, 1, Comparison.Greater, ExpectEqualComparator-100, 1, Comparison.Lower, ExpectEqualComparator1, -100, Comparison.Greater, ExpectEqualComparator-100, -1, Comparison.Lower, ExpectEqualComparator-1, -100, Comparison.Greater, // Extra tests if you like to challenge yourself! ExpectEqualComparator9007199254740992, 9007199254740992, Comparison.Equal, ExpectEqualComparator-9007199254740992, -9007199254740992, Comparison.Equal, ExpectEqualComparator9007199254740991, 9007199254740992, Comparison.Lower, ExpectEqualComparator9007199254740992, 9007199254740991, Comparison.Greater, ExpectEqualComparator-9007199254740992, -9007199254740991, Comparison.Lower, ExpectEqualComparator-9007199254740991, -9007199254740992, Comparison.Greater, ExpectEqualComparator3.1415, 3.1415, Comparison.Equal, ExpectEqualComparator3.1415, 3.1414, Comparison.Greater, ExpectEqualComparator0, 3.1414, Comparison.Lower, ExpectEqualComparator31.415, 3.1415, Comparison.Greater, ]可见官方测试不仅覆盖了正负整数组合还附带了“额外挑战”用例9007199254740992即2^53级别的大整数甚至小数如3.1415与3.1414。不过需要说明ComparatorA extends number, B extends number的约束本身只接受number类型而小数能否通过取决于实现如何截断/处理小数点后的内容若你希望严格支持小数需要在实现里对.进行专门处理见后文进阶一节。从源码结构看这些 extra 用例主要是为了引导实现者思考边界条件。二、解题思路从“元组计数”到“字符串逐位比较”2.1 为什么不能直接依赖运行时比较TypeScript 类型系统是结构化类型系统类型层面不存在a b这样的运算符。因此在类型空间比较两个数必须自己构造一种“可被类型推导的比较机制”。常见的三类思路元组长度计数法利用[...T, 0][length]构造长度为N的元组再让两个元组“互相消耗”来比较长短。这也是仓库中 Greater Than 这类中等级题目的常见套路其模板签名同样是type GreaterThanT extends number, U extends number any测试覆盖到GreaterThan1234567891011, 1234567891010见 test-cases.ts。模板字面量逐位比较把数字A、B转成字符串字面量类型A extends number ?${A}: never然后逐字符比较。本题标签为template-literal, math暗示官方推荐方向就是这种字符串化的逐位比较。BigInt 字符串运算如 Sumtype SumA extends string | number | bigint, B extends string | number | bigint string这类题目一样把数字当作字符串逐位做算术。本题只要求比较不要求加减所以逐位比较比逐位加减更直接。元组计数法在数字较大时会触发深度递归限制TS 默认递归深度约 1000 层这正是 2^53 级大整数用例存在的意义——它迫使你放弃元组计数改用字符串逐位比较。2.2 统一入口把number变成“符号 数字串”核心的第一步是符号分离。一个整数在类型层面可以表示为- 数字串负数或数字串非负数。我们可以先定义一个“拆符号”工具type SplitSignS extends string S extends -${infer R} ? [-, R] : [, S]这里-${infer R}是模板字面量类型的模式匹配若字符串以-开头就把剩余部分推断为R。例如SplitSign-123得到[-, 123]SplitSign123得到[, 123]。而把数字字面量类型转成字符串只需type ToStringN extends number ${N}三、核心算法四位一体的比较矩阵有了符号与数字串之后比较逻辑可以按“符号组合”分四类。这是本题与普通数字比较题最大的区别所在也是题目特意强调“even one is positive while another one is negative”的原因。3.1 符号不同一正一负正数 负数即 vs -恒为Greater负数 正数即- vs 恒为Lower。特殊情况是零0与-5比较时0应判定为Greater测试第 10 行Comparator0, -5 Greater。这里可以把0视为“非负”与正数同分支处理即可因为0与任何负数的比较结果都等价于“非负数 负数”。3.2 同为负数绝对值越小越大数学上-5 -7因为5 7。所以负数之间比较绝对值去掉负号后的数字串更小的反而更大。即- vs - 绝对值比较后取相反结果3.3 同为非负数直接逐位比较 vs 含零数字串逐位比较得到Greater / Equal / Lower原样返回。3.4 汇总符号组合结果一正一负正侧含 0正数侧为Greater同为负绝对值比较结果的反向同为非负绝对值比较结果原样因此整个Comparator可以被组织成type ComparatorA extends number, B extends number CompareBySignSplitSign${A}, SplitSign${B} type CompareBySignSA extends [ | -, string], SB extends [ | -, string] SA[0] extends SB[0] ? SA[0] extends - ? ReverseCompareAbsSA[1], SB[1] // 负负 绝对值反向 : CompareAbsSA[1], SB[1] // 正正 原样 : SA[0] extends - ? Comparison.Lower // 负 vs 正 : Comparison.Greater // 正 vs 负其中ReverseX负责把Greater / Lower对调、Equal保持不变type ReverseC C extends Comparison.Greater ? Comparison.Lower : C extends Comparison.Lower ? Comparison.Greater : Comparison.Equal这样核心问题就被归约为CompareAbsA, B对两个纯数字字符串不带符号进行逐位比较。四、核心实现CompareAbs逐位比较器4.1 先比位数再逐位比一个朴素的直觉是数字字符串长度更长值就更大。但要注意前导零问题如007vs8所以更稳健的做法是先去除前导零然后长度不同 长度长者大长度相同 从左到右逐位比较第一个不同字符大者大全部相同则相等。先定义去除前导零的工具type TrimZeroS extends string S extends 0 ? 0 : // 单独一个 0 保持为 0 S extends 0${infer R} ? TrimZeroR : S // 递归去掉前导 0再定义“构造 N 个字符的占位串”用于长度比较或直接用模板递归统计长度type LengthOfS extends string, T extends string S extends ${infer _}${infer R} ? LengthOfR, ${T}x : T[length]说明T[length]在模板字面量类型中是可用的TS 4.1这里利用字符串占位符间接获得“长度数字”。如果你更习惯元组也可以type ToTupleS extends string, T extends any[] [] S extends${infer _}${infer R}? ToTupleR, [0, ...T] : T然后用T[length]。元组方案在数字很大时会受限字符串占位方案更贴合本仓库其他 extreme 数学题的风格如 Sum 处理1_000_000_000_000n级别的字符串运算。逐位比较的核心递归type CompareDigitA extends string, B extends string A extends ${infer AH}${infer AT} ? B extends ${infer BH}${infer BT} ? AH extends BH ? CompareDigitAT, BT // 当前位相同继续下一位 : IsDigitGreaterAH, BH // 当前位不同分出胜负 : Comparison.Greater // A 更长同长度下 B 已耗尽 : B extends string ? B extends ? Comparison.Equal : Comparison.Lower : never其中IsDigitGreaterA, B把0..9映射到数字序数再比较。可以维护一个“数字到序号串”的映射表type DigitIndex { 0: ; 1: x; 2: xx; 3: xxx; 4: xxxx; 5: xxxxx; 6: xxxxxx; 7: xxxxxxx; 8: xxxxxxxx; 9: xxxxxxxxx } type IsDigitGreaterA extends string, B extends string DigitIndex[A keyof DigitIndex] extends ${DigitIndex[B keyof DigitIndex]}${infer _} ? A extends B ? Comparison.Equal : Comparison.Greater : Comparison.Lower说明xxxxx extends \${xx}${infer _}表示“5 的占位串能容纳 2 的占位串”即 5 2。同时用A extends B 兜底相等情形。更直接的做法是维护一张 10×10 的胜负表这里用占位串子串匹配减少枚举量。最终type CompareAbsA extends string, B extends string CompareAbsLenTrimZeroA, TrimZeroB type CompareAbsLenA extends string, B extends string LengthOfA extends LengthOfB ? CompareDigitA, B : GreaterThanLenLengthOfA, LengthOfB extends true ? Comparison.Greater : Comparison.LowerGreaterThanLen就是一个基于数字大小的比较可用元组或字符串占位实现见仓库 Greater Than 的思路。五、组装成完整Comparator及测试验证将上述模块拼装起来一个完整可用的参考实现如下enum Comparison { Greater, Equal, Lower, } type SplitSignS extends string S extends -${infer R} ? [-, R] : [, S] type TrimZeroS extends string S extends 0 ? 0 : S extends 0${infer R} ? TrimZeroR : S type ReverseC C extends Comparison.Greater ? Comparison.Lower : C extends Comparison.Lower ? Comparison.Greater : Comparison.Equal // 长度比较字符串占位版 type LenS extends string, T extends string S extends ${infer _}${infer R} ? LenR, ${T}x : T[length] type GreaterNumA extends number, B extends number /* 复用 GreaterThan 思路 */ any type CompareAbsLenA extends string, B extends string LenA extends LenB ? CompareDigitA, B : GreaterNumLenA, LenB extends true ? Comparison.Greater : Comparison.Lower type CompareDigitA extends string, B extends string /* 见 4.1 */ any type CompareAbsA extends string, B extends string CompareAbsLenTrimZeroA, TrimZeroB type CompareBySignSA extends [ | -, string], SB extends [ | -, string] SA[0] extends SB[0] ? SA[0] extends - ? ReverseCompareAbsSA[1], SB[1] : CompareAbsSA[1], SB[1] : SA[0] extends - ? Comparison.Lower : Comparison.Greater type ComparatorA extends number, B extends number CompareBySignSplitSign${A}, SplitSign${B}运行官方测试test-cases.ts时将上述类型与模板中给出的Comparator签名对齐再用ExpectEqual...断言。测试工具来自 utils/index.d.ts 中的Equal基于函数参数逆变位置的extends可赋值性双向比较与ExpectT extends true T任何一处返回错误枚举值都会让Equal得false从而Expect编译失败这就是本题“在线裁判”式验证的机制。一点工程化提示本仓库根 package.json 提供pnpm install后即可在本地用tsc依赖typescript: ^5.3.3见 package.json对test-cases.ts做类型检查来验证解法无需运行任何 JS。六、进阶讨论大整数、零与小数边界6.1 为什么 2^53 用例能通过字符串方案天然免疫精度问题9007199254740992是 JSNumber.MAX_SAFE_INTEGER 1超过它之后运行时数字已不可靠IEEE 754 双精度。但本题全程在类型空间把数字先字符串化再逐位比较从未把两个值放进同一个数值运算里因此 9007199254740992 与 9007199254740991 的逐位差异依然能被精确识别。这也是标签template-literal的精髓用字符串作为“无限精度”的载体。6.2 零的归属0在SplitSign中落入分支因为0不以-开头。这与测试期望完全吻合Comparator5, 0,→CompareAbs5,0→Greater✅Comparator0, -5 vs -→Greater✅Comparator-5, 0- vs →Lower✅Comparator0, 0CompareAbs0,0→Equal✅6.3 小数extra 用例的延伸测试文件最后还给出了3.1415、31.415等小数用例。从ComparatorA extends number, B extends number的签名看小数同样满足extends number。若要支持小数需要在SplitSign之后再对数字串做一次“小数点切分”比较规则变为先比较整数部分整数部分相等再比较小数部分小数部分同样逐位比较。这是对本题算法的一次自然扩展官方将其标记为 “Extra tests if you like to challenge yourself”即不作为强制验收项。6.4 与仓库其他数学题的呼应本仓库存在一条清晰的“类型级数学”题目线MinusOne 测试包含MinusOne9_007_199_254_740_992同样要求超出安全整数范围的类型级运算Sum 测试包含Sum1_000_000_000_000n, 123用字符串逐位加法处理 bigint 级输入Greater Than 测试包含GreaterThan1234567891011, 1234567891010。Integers Comparator 与它们共享同一套“字符串化 逐位递归”方法论凡是在运行时数字精度靠不住的场景类型级实现都可以借助模板字面量类型把数字当作字符串来处理。区别在于本题额外引入了符号维度让比较矩阵从一维变成了“符号 × 绝对值”的二维结构。七、小结实现Comparator的关键可以浓缩为四句话符号先行用-${infer R}把输入拆成“符号 数字串”同号再看绝对值同为负时对绝对值比较结果取反同为非负时原样返回逐位决胜去前导零后先比长度长度相同再逐位比较用占位串或映射表判断单字符大小用测试闭环借助 utils/index.d.ts 的Equal/Expect与 test-cases.ts 中的 30 组断言含正负、零、大整数、小数验证每一种符号组合。掌握这套“符号 字符串逐位”模板后你可以继续挑战 Sum、MinusOne 等更复杂的类型级算术题也可以把同样的模式迁移到 JSON 解析、URL 参数解析等其他 extreme 字符串题目中。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐Front-End Checklist 中的 const/let 规则告别 var用块级作用域与 lint 消除提升导致的隐患Front End Checklist 中的 const/let 规则告别 var用块级作用域与 lint 消除提升导致的隐患 本文围绕 Front End示例工程TypeScript 类型挑战type-challenges 题 112 之 CapitalizeWords 模板字面量类型实战解析TypeScript 类型挑战type challenges 题 112 之 CapitalizeWords 模板字面量类型实战解析 本文以 question示例工程type-challenges 中等题精解用模板字面量类型实现 CapitalizeTtype challenges 中等题精解用模板字面量类型实现 CapitalizeT 本篇文章基于开源仓库 type challengesCollect示例工程上一篇code-review-graph 图谱构建实战用 build-graph Skill 初始化与增量维护本地代码知识图谱下一篇Weex无障碍测试完整指南TalkBack与VoiceOver实战验证创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表