正则高尔夫内含 2 场可玩狩猎

正则高尔夫策略:如何打出低于标准杆的成绩

七种技巧,教你写出最短的正则:抓住每个怪物,放过其余单词——文中还有可以直接上手解题的可交互棋盘。

Regex Hunter 标志

· 阅读约 3 分钟

/^c.*t/

抓住

  • cart
  • chart
  • coat

放过

  • scat
  • crab

正则高尔夫是个简单的游戏:你会拿到两个单词列表。写出最短的正则表达式,匹配第一个列表里的所有单词(怪物),且不匹配第二个列表里的任何单词(无辜者)。每个字符都算数,每个棋盘都有一个标准杆——也就是你要挑战的长度。

诀窍在于,正则高尔夫其实考的不是正则语法,而是眼力。下面七个习惯,就是 28 个字符的答案和 5 个字符的答案之间的差距。每个示例都可以交互——直接在棋盘里输入,或者点击试试在沙盒中打开这个正则。

01描述差异,而不是描述单词

新手的做法是描述怪物。高手的做法是找出只有怪物才共有的最小特征。

对比缩短 82%

棋盘cat · cart · chart · coat · cast vs act · arc · tack · scat · crab

  1. 逐个列出能解出28
    /^(cat|cart|chart|coat|cast)$/
  2. 提取公共部分能解出20
    /^c(a|ar|har|oa|as)t$/
  3. 描述差异能解出5
    /^c.*t/
试试短的那个
同一个棋盘,三种答案。最短的那个根本没有描述单词——它只是把两组分开。

五个怪物都以 c 开头,并且后面某处有一个 t。无辜者都不满足。这一个发现就值 23 个字符。

精简阶梯:逐个列出单词要 28 个字符,提取公共部分要 20 个,描述差异只要 5 个

02锚点是你最便宜的过滤器

^ 和 $ 各只花一个字符,却能一下子排除整组无辜者。看看把上面答案里的 ^ 去掉会怎样:

匹配4/6 符合预期
/c.*t/

应当匹配3/3

  • cat
  • chart
  • coast

不应匹配1/3

  • act
  • scat
  • crab
在沙盒里试试
去掉 ^ 锚点后,“act”和“scat”混了进来——它们都含有一个 c,后面某处还跟着一个 t。

花两个字符加锚点,几乎总比把正则的其余部分写得更聪明要便宜。如果一个棋盘是按位置划分的——单词怎么开头、怎么结尾——先考虑锚点。

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 要求紧接着再出现同一个字符。

正则5 字符
/(.)\1/
在沙盒里试试
“任意一个字符,然后再来一次同样的字符”——单词中任意位置的双写字母。

在棋盘上试试。标准杆就是这个正则的长度:

可玩狩猎标准杆 5

抓住含有双写字母的单词。

目标 · 抓住 0/5

  • book目标
  • letter目标
  • coffee目标
  • balloon目标
  • grass目标

无辜词 · 误抓 0

  • bake无辜
  • paper无辜
  • stone无辜
  • orange无辜
  • tiger无辜

输入正则——棋盘会实时更新。

在沙盒里试试
喜欢吗?每天都有新的狩猎。玩今天的狩猎

06反向思考:说清楚什么不能出现

有时候,怪物的特征是它们缺少什么。“任何位置都没有 e”有两种写法,其中一种更短:

对比缩短 13%

棋盘moon · ask · dog · trunk · swift vs tree · eagle · green · apple · code

  1. 先行断言能解出8
    /^(?!.*e)/
  2. 否定字符类能解出7
    /^[^e]*$/
试试短的那个
两种都可行。否定字符类比先行断言短一个字符——在动用 (?!…) 之前先试试它。

当你需要同时满足多个条件时(“有数字并且没有空格”),先行断言才物有所值。如果只是缺一个字母,否定字符类更胜一筹。

07找不到共同点时,就覆盖整个集合

有些棋盘没有单一而优雅的规则。这时游戏就变成了一道覆盖谜题:找几个小片段——j、bu、a.a——每个都能抓住几个怪物且不碰任何无辜者,再用 | 把它们连起来。Peter Norvig 那个著名的求解器正是这么做的——我们在xkcd 的正则高尔夫:第 1313 期漫画背后的谜题一文中写过它。

08轮到你了

再来一个棋盘。怪物有共同的结构,而无辜者专门用来惩罚显而易见的猜测——(.)\1 会抓错单词,(.).\1 几乎什么都抓。你能达到标准杆吗?

可玩狩猎标准杆 6

抓住含有重复的两个字母片段的单词。

目标 · 抓住 0/5

  • banana目标
  • papaya目标
  • cocoa目标
  • dodo目标
  • haha目标

无辜词 · 误抓 0

  • abba无辜
  • llama无辜
  • salad无辜
  • tattoo无辜
  • cheese无辜

输入正则——棋盘会实时更新。

在沙盒里试试
喜欢吗?每天都有新的狩猎。玩今天的狩猎

09继续玩

  • 每天都有一场新的狩猎,还有标准杆等你挑战——玩今天的狩猎。
  • 练习包按技巧对棋盘分组——锚点、字符类、量词、反向引用——从基础开始。
  • 被语法卡住了?正则指南讲解了每一个语法元素,沙盒让你随意测试。