首页 / 资讯中心 / 文章详情

type-challenges 题解 114:用模板字面量类型实现 `CamelCase<T>`,完成 snake_case 到 camelCase 的转换

type-challenges 题解 114:用模板字面量类型实现 `CamelCase<T>`,完成 snake_case 到 camelCase 的转换 ★ FEATURED ARTICLE
示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载本文围绕 type-challenges 第 114 号挑战hard /#template-literal展开讲解如何用纯类型层面零运行时代码实现CamelCaseT将snake_case字符串类型转换为camelCase并正确处理大写归一、连续下划线、尾随下划线、非字母字符与空串等边界情况。读完本文你将掌握模板字面量类型的递归逐字符匹配套路、Lowercase/Uppercase固有类型的正确用法以及如何用type-challenges/utils的Equal做严格类型断言验证。题目速览需求与难度定位第 114 号挑战的英文原题见 questions/00114-hard-camelcase/README.md要求实现ImplementCamelCaseTwhich convertssnake_casestring tocamelCase.给出的两个示例type camelCase1 CamelCasehello_world_with_types // expected to be helloWorldWithTypes type camelCase2 CamelCaseHELLO_WORLD_WITH_TYPES // expected to be same as previous one值得注意的是第二个示例全大写输入HELLO_WORLD_WITH_TYPES与全小写输入得到完全相同的结果。这意味着CamelCaseT不只是“去掉下划线并把后一个字母大写”它还要把整段字符串统一归一为小写——只有紧跟下划线后的那个字母保持大写。从 questions/00114-hard-camelcase/info.yml 可以确认该题目的元数据difficulty: hard title: CamelCase tags: template-literal author: github: antfu name: Anthony Fu related: 612难度为 hard难点不在递归本身而在于对下划线“后面是什么字符”的分支判断稍有不慎就会在连续下划线、尾随下划线、非字母场景上翻车标签为template-literal解法完全基于模板字面量类型related: 612与 medium 难度、由 Johnson Chu 提出的 KebabCase612 互为逆操作本文最后会做对照分析。你的起点 questions/00114-hard-camelcase/template.ts 只有一行占位type CamelCaseS extends string any从测试用例读出的行为规范13 条边界用例挑战的“验收标准”全部写在 questions/00114-hard-camelcase/test-cases.ts 中共 13 条Equal断言。它们是本题最重要的需求文档逐条拆解如下输入期望输出行为要点foobarfoobar不含下划线原样保留小写FOOBARfoobar全大写需整体归一为小写foo_barfooBar基础场景下划线删除后一字母大写foo__barfoo_Bar连续下划线只保留一个_且其后字母仍大写foo_$barfoo_$bar_后是非字母$下划线保留、不触发大写foo_bar_fooBar_尾随下划线无后随字符下划线保留foo_bar__fooBar__两个尾随下划线都保留foo_bar_$fooBar_$中间正常转换_$整体原样保留foo_bar_hello_worldfooBarHelloWorld多段连续转换HELLO_WORLD_WITH_TYPEShelloWorldWithTypes大写输入与首例结果一致--非字母单字符原样保留空串返回空串递归终止emoji 等非 ASCII 字符原样保留综合这些用例可以归纳出四条硬性规则下划线_本身不进入输出除非它后面不是字母或后面没有字符_后紧跟字母时删除_并把该字母转大写普通字母一律归一为小写包括全大写输入非字母字符$、-、emoji 等原样保留且它们前面的_不会被吞掉。解题思路单字符递归 下划线“触发大写”模板字面量类型做字符串处理的核心套路是逐字符递归。用S extends${infer Head}${infer Rest} 把字符串拆成“第一个字符 剩余部分”处理完Head后把Rest交给CamelCase自身继续递归当字符串为空无法匹配拆解模式时直接返回S作为终止条件。在此基础上对Head分两条主路径普通字符输出LowercaseHead继续处理剩余部分下划线_窥视下一个字符Next根据它的“身份”决定行为——这也正是 hard 难度的核心。处理_时需要三个分支Next是字母 → 输出UppercaseNext丢弃_继续处理TailNext不是字母如$、_→ 保留_原样输出Next继续处理Tail没有Next尾随下划线→ 直接输出_。其中“Next是字母”的判断不能靠UppercaseNext extends Next之类的朴素写法需要专门设计一个IsLetter辅助类型。源码级实现辅助类型与主类型第一步判断单字符是否为字母的IsLetter借助 TypeScript 的固有类型intrinsic typesLowercase与Uppercase可以做到对任意单字符的可靠判断type IsLetterC extends string LowercaseC extends UppercaseC ? false : true原理如果C是字母那么它的大小写形式一定不同即LowercaseC不 extendsUppercaseC结果为true如果C是$、_、-、emoji 等没有大小写映射的字符两者相等结果为false。可以快速验证type T1 IsLetterb // true type T2 IsLetterB // true type T3 IsLetter$ // false type T4 IsLetter_ // false type T5 IsLetter // false注意这里不能用C extends UppercaseC单向判断因为小写字母bextendsB为 false 却仍是字母也不能用C extends LowercaseC因为大写字母Bextendsb同样为 false。只有“大小写映射不同”才是字母的充要条件。第二步CamelCaseS主类型type CamelCaseS extends string S extends ${infer Head}${infer Rest} ? Head extends _ ? Rest extends ${infer Next}${infer Tail} ? Next extends _ ? _${CamelCase_${Tail}} // 连续下划线保留一个 _重新进入 _ 处理 : IsLetterNext extends true ? ${UppercaseNext}${CamelCaseTail} // 字母丢弃 _大写该字母 : _${Next}${CamelCaseTail} // 非字母_ 与 Next 原样保留 : _ // 尾随下划线 : ${LowercaseHead}${CamelCaseRest} // 普通字符归一为小写 : S相比朴素解法这里特意增加了一层Next extends _分支来处理连续下划线。以foo__bar期望foo_Bar为例当第一个_窥视到的Next是第二个_时我们保留第一个_然后把_Tail重新交给CamelCase递归这样第二个_会再走一遍“下划线分支”发现它后面是字母b于是将其吞掉并大写为B最终得到foo_Bar——与测试用例完全吻合。关键路径逐步推演用例一foo_bar_hello_world→fooBarHelloWorldf o o → foo逐字 Lowercase原样 _ 后跟 b字母 → B丢弃 _ ar → ar _ 后跟 h字母 → H ello_world 递归 → elloWorldw 同样被大写 结果fooBarHelloWorld ✓用例二HELLO_WORLD_WITH_TYPES→helloWorldWithTypes普通字符路径中的LowercaseHead会把每个大写字母逐个归一为小写因此HELLO→hello下划线后的大写字母W、T走UppercaseNext分支——UppercaseW仍是W于是三处“首字母”保持大写得到与全小写输入完全一致的结果。用例三foo_$bar→foo_$bar_窥视到的Next是$IsLetter$为 false进入“非字母”分支输出_$后面的bar原样递归整体不做任何改写。用例四foo_bar_→fooBar_与→foo_bar_中最后一个_没有Next落入_分支保留尾随下划线无法匹配${infer Head}${infer Rest}直接返回S即空串这也是整个递归的终止条件。边界情况与设计取舍为什么非字母后不触发大写Uppercase本身也能“接收”$、-、emoji原样返回如果直接无脑UppercaseNextfoo_$bar会被错误地吞掉_。引入IsLetter过滤保证只有真正的字母才享受“吃掉下划线、切换大写”的待遇。连续下划线要“合二为一”这是最容易写错的地方。朴素实现对任何非字母都原样保留会把foo__bar变成foo__Bar而期望是foo_Bar。只有把连续下划线“压缩”为单个_并让后续字母正常大写才能通过第 4 条用例。全大写输入必须归一第 2、10 条用例要求CamelCaseFOOBAR与CamelCaseHELLO_WORLD_WITH_TYPES分别等于foobar和helloWorldWithTypes因此普通字符路径必须显式LowercaseHead而不是原样拼接。空串与单字符、-、三条用例保证了递归必须有明确的终止分支且所有非下划线字符包括 emoji 这类 BMP 之外的码点都能原样通过——Lowercase/Uppercase对它们返回自身。与 KebabCase612的互为逆操作info.yml中的related: 612指向另一道字符串处理题 KebabCasemedium把camelCase/PascalCase字符串替换为kebab-case如FooBarBaz→foo-bar-baz。对比两道题的测试用例见 questions/00612-medium-kebabcase/test-cases.ts可以发现它们的设计哲学完全对称维度CamelCase114hardKebabCase612medium转换方向snake_case→camelCasecamelCase/PascalCase→kebab-case字母处理统一小写_后字母大写遇到大写字母前插入-并转小写共享边界空串、-、均原样返回空串、-、均原样返回输入归一FOOBAR→foobarABC→a-b-c逐字母转小写从源码结构看两道题都以“模板字面量 单字符递归”为骨架区别只在于对“下一个字符是大写字母”的识别逻辑KebabCase 需要识别大写字母从而插入分隔符CamelCase 需要识别下划线从而决定是否吞并并大写。KebabCase 比 CamelCase 少一个“连续分隔符压缩”的分支Foo-Bar→foo--bar连字符被完整保留这也是 612 只有 medium 难度的原因之一。如何验证你的实现本题没有任何运行时测试验证方式是类型层面的编译期断言。测试文件 questions/00114-hard-camelcase/test-cases.ts 从type-challenges/utils引入Equal与Expectimport type { Equal, Expect } from type-challenges/utils type cases [ ExpectEqualCamelCasefoobar, foobar, ExpectEqualCamelCaseFOOBAR, foobar, // ... 共 13 条 ]Equal的定义位于 utils/index.d.ts采用经典的“函数签名逆变比较”技巧来判定两个类型是否严格相等export type EqualX, Y (T() T extends X ? 1 : 2) extends (T() T extends Y ? 1 : 2) ? true : false只有当CamelCase...产出的字面量类型与期望字符串逐字精确一致包括大小写与下划线位置时Equal才为trueExpect才通过编译。因此只要你的实现能通过这套断言就说明它对 13 条边界用例的行为全部正确。本地验证方式为在仓库根目录安装依赖pnpm install后让 TypeScript 编译器检查该测试文件即可项目使用 pnpm workspacetype-challenges/utils以workspace:*形式声明于 package.json。整个仓库的所有挑战题目都遵循同一套template.tstest-cases.tsinfo.yml结构理解 114 题的解法后你可以把同样的递归模式迁移到其他模板字面量类题目中。小结CamelCaseT看似只是“去掉下划线、后面字母大写”但 13 条测试用例揭示出远超直觉的细节大小写归一、连续下划线压缩、尾随下划线保留、非字母防误吞。本文给出的实现通过“单字符递归 IsLetter分支判断 连续下划线特殊处理”三块拼图完整覆盖了全部用例并借助type-challenges/utils的Equal完成了编译期验证。建议你亲自在 template.ts 中从type CamelCaseS extends string any起步先自行尝试再对照本文实现推演foo__bar、HELLO_WORLD_WITH_TYPES等关键路径最后挑战它的“逆操作” KebabCase612 检验举一反三的能力。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐type-challenges 中等题精解用模板字面量类型实现 CapitalizeTtype challenges 中等题精解用模板字面量类型实现 CapitalizeT 本篇文章基于开源仓库 type challengesCollect示例工程type-challenges 题解用模板字面量类型实现 CapitalizeT将字符串首字母大写type challenges 题解用模板字面量类型实现 CapitalizeT 将字符串首字母大写 导读 本篇文章围绕 type challenges示例工程type-challenges 题解110/medium用模板字面量类型实现 CapitalizeT 字符串首字母大写type challenges 题解110/medium用模板字面量类型实现 CapitalizeT 字符串首字母大写 Capitalize 是 typ示例工程上一篇芋道源码终极指南Spring Boot企业级开发框架实战教程下一篇芋道源码Spring Boot企业级开发框架终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站