正则高尔夫策略:如何打出低于标准杆的成绩
七种技巧,教你写出最短的正则:抓住每个怪物,放过其余单词——文中还有可以直接上手解题的可交互棋盘。

· 阅读约 3 分钟
/^c.*t/
抓住
- cart
- chart
- coat
放过
- scat
- crab
正则高尔夫是个简单的游戏:你会拿到两个单词列表。写出最短的正则表达式,匹配第一个列表里的所有单词(怪物),且不匹配第二个列表里的任何单词(无辜者)。每个字符都算数,每个棋盘都有一个标准杆——也就是你要挑战的长度。
诀窍在于,正则高尔夫其实考的不是正则语法,而是眼力。下面七个习惯,就是 28 个字符的答案和 5 个字符的答案之间的差距。每个示例都可以交互——直接在棋盘里输入,或者点击试试在沙盒中打开这个正则。
01描述差异,而不是描述单词
新手的做法是描述怪物。高手的做法是找出只有怪物才共有的最小特征。
棋盘cat · cart · chart · coat · cast vs act · arc · tack · scat · crab
- 逐个列出能解出28
/^(cat|cart|chart|coat|cast)$/ - 提取公共部分能解出20
/^c(a|ar|har|oa|as)t$/ - 描述差异能解出5
/^c.*t/
五个怪物都以 c 开头,并且后面某处有一个 t。无辜者都不满足。这一个发现就值 23 个字符。
02锚点是你最便宜的过滤器
^ 和 $ 各只花一个字符,却能一下子排除整组无辜者。看看把上面答案里的 ^ 去掉会怎样:
花两个字符加锚点,几乎总比把正则的其余部分写得更聪明要便宜。如果一个棋盘是按位置划分的——单词怎么开头、怎么结尾——先考虑锚点。
03是搜索,不是整词匹配
正则高尔夫用的是搜索语义:你的正则只需要在单词的某个位置匹配上,不必匹配整个单词。所以没必要把所有东西都包在 ^…$ 里,也不用拿 .* 凑数:
| 不要写 | 改写为 | 节省 |
|---|---|---|
^.*ing$ |
ing$ |
3 |
.*foo.* |
foo |
4 |
^(a|b) |
^[ab] |
1 |
开头和结尾的 .* 纯属凑数。(Regex Hunter 的诊断面板正是因此会标出它们。)
04把字符花在刀刃上
很多正则功能都有短写法和长写法。高尔夫玩家永远选短的:
| 长 | 短 | 节省 |
|---|---|---|
[0-9] |
\d |
3 |
[A-Za-z0-9_] |
\w |
10 |
(a|b|c) |
[abc] |
2 |
.{2} |
.. |
2 |
aa* |
a+ |
1 |
(?:ab)+ |
(ab)+ |
2 |
在生产代码里,非捕获组是好习惯——在高尔夫里,它们就是浪费的字符。
05让反向引用替你发现规律
如果怪物共有的是一种形状,而不是特定的字母——比如双写字母、重复的片段——反向引用就能搞定。(.) 捕获任意一个字符;\1 要求紧接着再出现同一个字符。
/(.)\1/在棋盘上试试。标准杆就是这个正则的长度:
抓住含有双写字母的单词。
目标 · 抓住 0/5
- book目标
- letter目标
- coffee目标
- balloon目标
- grass目标
无辜词 · 误抓 0
- bake无辜
- paper无辜
- stone无辜
- orange无辜
- tiger无辜
输入正则——棋盘会实时更新。
06反向思考:说清楚什么不能出现
有时候,怪物的特征是它们缺少什么。“任何位置都没有 e”有两种写法,其中一种更短:
棋盘moon · ask · dog · trunk · swift vs tree · eagle · green · apple · code
- 先行断言能解出8
/^(?!.*e)/ - 否定字符类能解出7
/^[^e]*$/
当你需要同时满足多个条件时(“有数字并且没有空格”),先行断言才物有所值。如果只是缺一个字母,否定字符类更胜一筹。
07找不到共同点时,就覆盖整个集合
有些棋盘没有单一而优雅的规则。这时游戏就变成了一道覆盖谜题:找几个小片段——j、bu、a.a——每个都能抓住几个怪物且不碰任何无辜者,再用 | 把它们连起来。Peter Norvig 那个著名的求解器正是这么做的——我们在xkcd 的正则高尔夫:第 1313 期漫画背后的谜题一文中写过它。
08轮到你了
再来一个棋盘。怪物有共同的结构,而无辜者专门用来惩罚显而易见的猜测——(.)\1 会抓错单词,(.).\1 几乎什么都抓。你能达到标准杆吗?
抓住含有重复的两个字母片段的单词。
目标 · 抓住 0/5
- banana目标
- papaya目标
- cocoa目标
- dodo目标
- haha目标
无辜词 · 误抓 0
- abba无辜
- llama无辜
- salad无辜
- tattoo无辜
- cheese无辜
输入正则——棋盘会实时更新。