Yuzhe's Blog

yuzhes

Drop Char(删除字符)

Drop Char(删除字符)

题目链接

题目

从字符串中删除指定的字符。

type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'

解法

type DropChar<S extends string, C extends string> =
  S extends `${infer L}${C}${infer R}`
    ? DropChar<`${L}${R}`, C>
    : S

核心思路:用模板字面量推断,每次找到并删掉一个 C,然后递归处理,直到字符串中不再含有 C 为止。

逐步拆解

第一步 — 模板字面量模式匹配

S extends `${infer L}${C}${infer R}`

TypeScript 会尝试把 S 拆成三段:

只要 C 出现在 S 中,这个条件就成立,我们就能拿到 LR

第二步 — 去掉 C 后递归

? DropChar<`${L}${R}`, C>

LR 拼回去,跳过 C,再递归处理下一个出现的 C

第三步 — 终止条件

: S

S 中找不到 C 时,extends 检查失败,直接返回 S

执行轨迹示例

DropChar<' b u t ', ' '>
'' + 'b u t '   → DropChar<'b u t ', ' '>
'b' + 'u t '    → DropChar<'bu t ', ' '>
'bu' + 't '     → DropChar<'but ', ' '>
'but' + ''      → DropChar<'but', ' '>
'but'(找不到 ' ',直接返回)

深入理解

为什么单字符 C 工作得很顺畅?

本题的 C单个字符(长度为 1 的字符串字面量)。这很关键:当 C 是单字符时,模板字面量 ${infer L}${C}${infer R} 的分割唯一且确定——TypeScript 总是从最左侧的出现开始匹配,使递归行为完全可预测。

如果 C 是多字符子串,同样的模式也能处理,这正是更难的姊妹题 Drop String (2059-hard) 探索的方向(那道题中 C 是多个字符的联合类型)。

模板字面量类型 = 字符串的模式匹配

TypeScript 的模板字面量推断(${infer X})本质上是对字符串类型做结构化模式匹配。几点要注意:

  1. 左起贪心${infer L}${C}${infer R} 中,L 尽可能短(匹配最左侧的 C)。
  2. 精确字面量匹配${C} 匹配 C 的精确字符串值。C = ' ' 时,TypeScript 就找字面量空格。
  3. 支持递归 — 条件类型本身可以递归,因此可以遍历整个字符串。

另一种写法:逐字符处理

也可以一次只处理一个字符,逻辑更直观:

type DropChar<S extends string, C extends string> =
  S extends `${infer Head}${infer Tail}`
    ? Head extends C
      ? DropChar<Tail, C>
      : `${Head}${DropChar<Tail, C>}`
    : S

每次推断出第一个字符 Head:若等于 C 就跳过,否则保留。这种写法更啰嗦,但对”字符逐个处理”的逻辑更显式。

实际中推荐用双 infer 版本,更简洁。

与其他字符串操作题的关系

题目技术要点
TrimLeft (106)删除开头空白:${' ' | '\n' | '\t'}${infer R}
Trim (108)同时删两端
Replace (116)替换第一个出现
ReplaceAll (119)替换所有出现(和 DropChar 同一递归模式)
DropChar (2070)删除所有指定字符
DropString (2059-hard)删除字符集合中的所有字符

DropChar<S, C> 本质上就是 ReplaceAll<S, C, ''>——把每个 C 替换成空字符串。

TypeScript 递归深度限制

TypeScript 的条件类型递归有深度上限(大约 1000 次实例化)。对于极长的字符串,理论上会超出限制。但实际项目中字符串长度远不到这个量级,完全不用担心。

核心要点