news 2026/10/2 1:56:28

TypeScript 类型挑战 04260 深度解析:用模板字面量类型实现 AllCombinations 全排列组合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeScript 类型挑战 04260 深度解析:用模板字面量类型实现 AllCombinations 全排列组合
  • 示例工程

【免费下载链接】type-challenges

Collection of TypeScript type challenges with online judge

项目地址:https://gitcode.com/GitHub_Trending/ty/type-challenges
点击查看免费下载

在 TypeScript 类型体操仓库 type-challenges 中,04260 是一道综合考察模板字面量类型、infer推断与联合类型分发的 Medium 级题目。本文将围绕题目说明 README.ja.md 展开,结合仓库配套的测试用例与工具类型实现,完整推导并给出一个可运行、可验证的AllCombinations<S>类型。读完你不仅能独立解出本题,还能掌握用类型系统做递归枚举与排列组合的通用套路。

题目速览:AllCombinations 是什么

根据 info.yml 的元数据,这道题的关键信息如下:

字段值
难度medium(日文版标注为「中級」)
标题AllCombinations(日文标题:文字の組み合わせ)
作者蛭子屋双六(sugoroku-y)
标签template-literal、infer、union

题面要求(原文见 README.ja.md):实现类型AllCombinations<S>,返回"使用S中的每个字符最多一次所能构成的所有字符串组合"(原文:指定された文字列に含まれる文字をそれぞれ最大で1度だけ使った文字列のすべての組み合わせ)。

题目给出的示例:

type AllCombinations_ABC = AllCombinations<'ABC'>; // should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' // | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'

从结果可以提炼出三条核心语义:

  1. 空字符串也是组合:结果始终包含'';
  2. 每个字符至多使用一次:因此不会出现'AA'、'AAB'这类重复使用字符的串;
  3. 顺序敏感,本质是"排列"而非"组合":'AB'与'BA'是两条独立结果,说明该类型枚举的是"所有子集的全体全排列"。

用测试用例锁定输入输出的精确边界

本仓库为每道题都配套了template.ts与test-cases.ts。本题的起点模板 template.ts 只有一行占位:

type AllCombinations<S> = any

所有逻辑都需要由解题者补齐。而 test-cases.ts 给出了 5 个判定用例:

import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<AllCombinations<''>, ''>>, Expect<Equal<AllCombinations<'A'>, '' | 'A'>>, Expect<Equal<AllCombinations<'AB'>, '' | 'A' | 'B' | 'AB' | 'BA'>>, Expect<Equal<AllCombinations<'ABC'>, /* 共 16 个结果 */>>, Expect<Equal<AllCombinations<'ABCD'>, /* 共 65 个结果 */>>, ]

由此可以确认几个关键事实:

  • 断言依赖Equal与Expect,二者来自@type-challenges/utils工具包,其实现位于 utils/index.d.ts。Expect<T extends true> = T负责把类型约束为true,而Equal利用"同一泛型函数在分别代入X、Y时返回类型是否一致"来判断两个类型严格相等:
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false
  • 结果数量符合数学规律:设字符数为n,输出为所有子集的全排列之并,即Σ P(n, k)(k 从 0 到 n,P(n,k) = n!/(n-k)!)。因此n=1时 1+1=2 项,n=2时 1+2+2=5 项,n=3时 1+3+6+6=16 项,n=4时 1+4+12+24+24=65 项。这也解释了为什么'ABCD'一行的期望结果最长——它是 65 个字符串字面量的联合。

解题拆解(一):StringToUnion —— 用模板字面量 + infer 拆分字符

模板字面量类型的核心能力,是在字符串上进行模式匹配与子串推断。第一步是把字符串S转成"字符联合类型",它是后续所有递归枚举的燃料:

type StringToUnion<S extends string> = S extends `${infer C}${infer Rest}` ? C | StringToUnion<Rest> : never

原理说明:

  • `${infer C}${infer Rest}`是模板字面量推断:只要S非空,C就会被推断为首字符,Rest被推断为剩余部分;
  • 联合分支C | StringToUnion<Rest>逐层"剥壳",把每个字符并入联合;
  • 基线分支:空字符串无法匹配该模板,返回never。

例如StringToUnion<'ABC'>会得到'A' | 'B' | 'C'。这是仓库内大量字符串处理题目的通用前置手法(例如00106-medium-trimleft、00298-medium-length-of-string等都是以模板字面量拆分字符串为起点的)。

解题拆解(二):递归枚举 —— 映射类型 + Exclude 构成排列树

拿到字符联合U之后,问题就转化为"依次挑选下一个字符是谁"的递归枚举:

  • 用映射类型遍历U中的每一个字符K;
  • 一旦选定K,剩余可用的字符集合就是Exclude<U, K>;
  • 递归地对剩余集合继续枚举,并把当前字符拼在前面:`${K}${AllCombinations<never, Exclude<U, K>>}`;
  • 基线条件:当剩余字符为空(U为never)时,唯一结果是''。

这里有两个容易踩坑的细节:

  1. never会短路裸条件类型:U extends never ? ... : ...这类裸露条件类型会因为never没有成员而整体变为never,导致递归无法终止。因此必须用元组包装[U] extends [never]来显式判断"已无剩余字符"的基线;
  2. 空串必须参与每一层递归:只有当子问题的结果包含''时,`${K}${''}`才能还原出单个字符K;最终在顶层再并上'',即可覆盖所有子集。

参考答案与逐层推演

综合以上思路,一份可运行、能通过全部 5 个用例的实现如下:

type StringToUnion<S extends string> = S extends `${infer C}${infer Rest}` ? C | StringToUnion<Rest> : never type AllCombinations< S extends string, U extends string = StringToUnion<S> > = [U] extends [never] ? '' // 基线:无可用字符 : '' | { [K in U]: `${K}${AllCombinations<never, Exclude<U, K>>}` }[U]

以AllCombinations<'ABC'>手动推演一遍:

  • 首层U = 'A' | 'B' | 'C',映射类型对三个字符分别展开:
    • K = 'A':'A' | AllCombinations<never, 'B' | 'C'>='A' | 'AB' | 'ABC' | 'AC' | 'ACB'
    • K = 'B':'B' | AllCombinations<never, 'A' | 'C'>='B' | 'BA' | 'BAC' | 'BC' | 'BCA'
    • K = 'C':'C' | AllCombinations<never, 'A' | 'B'>='C' | 'CA' | 'CAB' | 'CB' | 'CBA'
  • 外层再并上'',恰好得到 16 个结果,与测试期望完全一致(联合类型本身无序,成员数量与内容一致即通过Equal断言)。

'ABCD'按同一规则展开到 65 项。由于每一步都通过Exclude<U, K>把已选字符从可用集合中移除,"每个字符最多使用一次"的约束在类型层面被天然保证。

关键技巧总结

这道 Medium 题浓缩了四个高频考点,同样适用于仓库中大量字符串与联合类型的题目:

  1. 模板字面量 + infer 拆分:S extends `${infer C}${infer Rest}`是处理字符串的万能入口;
  2. 分布式条件类型:联合类型在裸条件类型中自动分发,是"对每个字符分别处理"的基础;
  3. 元组包装防分发:[U] extends [never]避免never短路,确保递归存在明确的终止分支;
  4. Exclude做减法:把"已用过"的字符从可用集合中剔除,是编码"至多一次"约束的关键。

延伸阅读

  • 本题题面:英文版 与 日文版;
  • 题目元数据与标签:info.yml;
  • 起手模板与验证用例:template.ts、test-cases.ts;
  • 断言工具Expect/Equal的完整实现:utils/index.d.ts;
  • 仓库根目录 guides 下预置了与本题三大主题对应的指南文件:infer.md、key-in.md、recursive.md,当前仍为 TODO 占位,可作为后续深入学习的入口。
  • 示例工程

【免费下载链接】type-challenges

Collection of TypeScript type challenges with online judge

项目地址:https://gitcode.com/GitHub_Trending/ty/type-challenges
点击查看免费下载
上一篇:15款顶级Android进度条Progressbar动态效果库推荐:从基础到高级全攻略
下一篇:《Python Machine Learning》(第2版)勘误手册:逐条解读修正内容与源码验证

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 1:55:10

Win10产品密钥查找原理与安全提取实战指南

1. 项目概述&#xff1a;为什么Win10产品密钥查找这件事&#xff0c;比你想象中更值得深挖Win10怎么查找产品密钥&#xff1f;这个问题看似简单&#xff0c;但背后藏着Windows激活机制、系统安全边界、硬件绑定逻辑和用户数据主权的多重博弈。我做系统部署和企业IT支持十多年&a…

作者头像 李华
网站建设 2026/10/2 1:54:56

基于C#与MySQL的仓库管理系统实战:从建库到入库单落地的完整路径

简介&#xff1a;这份资源是一套基于C#与MySQL数据库开发的仓库管理系统完整项目包&#xff0c;面向学习C#面向对象编程、数据库设计及企业级应用开发的学生与开发者&#xff0c;可用于课程设计、毕业设计或自学实践。压缩包共163个文件&#xff0c;约1.22MB&#xff0c;以49个…

作者头像 李华