Regex Golf刷题笔记
零、前言
笔记创建时间:2020/03/10
笔记更新时间:2022/01/07
网站:Regex Golf
答案汇总:Best known Regex Golf solutions
一、Warmup
规律:左边的字符串都包含 foo
子串,右边的字符串都不包含。
答案:
- 长度:3
foo
解析:仅匹配包含 foo
子串的字符串。
- 长度:3
f.o
解析:与上一条正则表达式不同的是中间的 o
替换为了任意字符 .
,表示匹配任何包含 f + 一个任意字符 + o
形式子串的字符串。
二、Anchors
规律:左边的字符串都以 k
结尾,右边的字符串都不以 k
结尾。
答案:
- 长度:2
k$
解析:使用结尾符 $
限定匹配仅结尾为 k
的字符串。
三、It never ends
规律:左边的字符串都以 u
结尾,右边的字符串都不以 u
结尾。
答案:
- 长度:3
u\b
解析:这一题跟上一题规律差不多,但这题规定不能使用结尾符 $
,所以第一个想到的就是使用单词边界符 \b
来匹配,不过长度相对结尾符 $
来说就多了一个字符。
- 长度:6
u(?!.)
解析:虽然这条正则表达式没有上一条短,但给出了一个新的思路(使用正向否定预查任意字符来匹配字符串末尾),值得学习。
四、Ranges
规律:左边的所有字符串都只涉及 a
到 f
这几个字母。
答案:
- 长度:8
[a-f]{4}
解析:使用 [a-f]
限定匹配字符为 a
到 f
,并用 {4}
重复匹配 4 次。
- 长度:8
^[a-f]*$
解析:这一条正则表达式同样使用 [a-f]
限定匹配字符为 a
到 f
,但改为用 *
重复匹配任意次来将原本的 {4}
缩短了 2 个字符,但任意次(0 次也可以被匹配)也导致右边的字符串都会被匹配上,如果换成 +
,也是只要包含了哪怕一个 a
到 f
的字母就会被匹配上。所以首尾加上开始符 ^
和结束符 $
来限制所有字符必须均为 a
到 f
才能被匹配,到头来正则表达式的长度并没有得到缩减,但也是另一种思路。
五、Backrefs
规律:左边的字符串都包含 ABC + 任意个字符 + ABC
形式的子串,右边的字符串则没有。
答案:
- 长度:9
(...).*\1
解析:用 (...)
来匹配并捕获开头的 ABC
部分,中间的任意个字符则用 .*
匹配,最后结尾的 ABC
使用前面捕获到 \1
来匹配。
六、Abba
规律:右边的字符串都包含 任意个字符 + ABBA
形式的子串,左边的字符串则没有。
答案:
- 长度:17
^(?!.*(.)(.)\2\1)
解析:因为左边为需要匹配到的字符串,而右边为同时不能匹配到的字符串。这次的规律在右边,所以需要排除包含 任意个字符 + ABBA
形式子串的字符串。因此使用正向否定预查 (?!)
来排除。而 ABBA
的部分则用两个 (.)
分别匹配并捕获前面的 A
和 B
,并用 \2
和 \1
来匹配后面的 BA
。而所有类型的预查都不消耗字符,所以在开头加上开始符 ^
。而且并不是所有需要排除的字符串的 ABBA
部分都在开头,所以在 (.)(.)\2\1
的前面加上 .*
来匹配任意个字符。
- 长度:16
^(?!(.)+(.)\2\1)
【重点】
例如表达式:(.)+\1
解释:其中+
会匹配一次或多次()
中的部分.
,而()
捕获的内容为+
匹配到的最后一个。
解析:因 (.)+
组合时, +
会匹配一次或多次 ()
中的 .
,而 ()
捕获到的内容为 +
最后匹配到的内容,所以很巧妙的将长度缩短了 1 个字符。
- 长度:14
^(?!(.)+\1)|ef
解析:这条正则表达式前面的 ^(?!(.)+\1)
部分根据上一条所介绍的原理其实也不难想到,它是将原本的 (.)+(.)\2\1
匹配 任意个字符 + ABBA
形式的子串转变为匹配 任意个字符 + BBA
形式的子串。但左边的 effusive
和 noisefully
两个字符串由于也包含了 BBA
形式的子串,导致未被匹配到。而细心观察就可以发现,只有这两个字符串中包含 ef
子串,所以用或符号 |
连接 ef
即可。正则表达式被缩短到了 14 个字符。
七、A man, a plan
规律:左边的字符串都为 回文字符串
,右边的字符串则没有。
答案:
- 长度:14
^(.)(.).*\2\1$
解析:虽然左边的字符串都为 回文字符串
,但其实可以简化为 AB + 任意个字母 + BA
的形式,所以使用两个 (.)
分别匹配并捕获前面的 A
和 B
,中间的任意个字母则用 .*
匹配,并用 \2
和 \1
来匹配后面的 BA
部分。但为了防止右边的字符串中包含相同形式的子串,所以在开头和结尾分别添加了开始符 ^
和结尾符 $
来确保整个字符串都为 AB + 任意个字母 + BA
的形式才会被匹配。
- 长度:13
^(.)[^p].*\1$
解析:这一题其实也可以简化成 A + 任意个字母 + A
的形式。所以可以直接使用 (.).*\1
来匹配,但会发现右边的第 6、9、11、13 条字符串也被匹配到了。所以在开头和结尾分别加上 ^
和 $
来防止,但右边的还有 sporous
这个字符串被匹配到,但可以发现左边的字符串中没有第二个字母为 p
的,所以使用 [^p]
来排除。
八、Prime
规律:左边字符串 x
的个数均为素数个,右边则均不为素数个。
答案:
- 长度:14
^(?!(..+)\1+$)
解析:使用正向否定预查 (?!)
排除非素数个 x
的字符串。其中 (..+)
可以匹配并捕获 [2,N]
个任意字符,再在后面使用 \1
匹配捕获到的内容,则变为 2[2,N]
个任意字符,然后还有一个 +
跟在 \1
后面,则变为 [2,N][2,N]
个任意字符,最后有一个结尾符 $
表示直到字符串结尾。
九、Four
规律:左边的字符串都包含 ABACADA
形式的子串,右边的字符串则没有。
答案:
- 长度:12
(.).\1.\1.\1
解析:使用 (.)
匹配并捕获 A
,使用 '\1' 来匹配后续的 A
,其中穿插的 B
、 C
、 D
则用 .
匹配任意字符。
- 长度:11
(.)(.\1){3}
解析:根据上一条正则表达式,可以发现其中 .\1
部分出现了 3 次,所以可以将其合并并用 {3}
来重复匹配 3 次,将正则表达式缩短了 1 个长度。
十、Order
规律:左边的字符串长度都是 5 或 6,而右边的字符串只有 oriole
的长度是 6 。
答案:
- 长度:11
^[^o].{4,5}$
解析:首先根据发现的规律很容易写出 ^.{5,6}$
,但右边的 oriole
字符串因为是 6 个长度也会被匹配上。进一步可以发现左边的字符串第一个字符都没有 o
,那就好办了,在前面添加 [^o]
来排除第一字符是 o
的情况,那么重复区间同时也要相应的减 1 ,改为 {4,5}
。
- 长度:11
^.{5}[^e]?$
解析:根据刚总结的规律,右边只有 oriole
字符串因为是 6 个长度也会被 ^.{5,6}$
匹配上,但可以发现不仅左边字符串第一个字符都没有 o
,而且左边长度为 6 的字符串中第六个字符都没有 e
,所以在后面添加 [^e]
来排除第六个字符是 e
的情况,那么重复区间同时也要相应的减 1 ,改为 {5}
。但左边字符串也有长度为 5 的情况,所以需要在后面添加 ?
表示排除第六个字符为 e
或没有第六个字符。
首先使用 ^.{5}
从字符串的开始匹配 5 个任意字符,然后用 [^e]
排除了
十一、Triples
规律:左右两边的字符串都是数字,并且左边的都是 3 的倍数,而右边的不是。
答案:
- 长度:36
00([369]|1[25]|$)|.1.+4|3.*7.|4.2|55
- 长度:34
00($|3|6|9|12|15)|4.2|.1.+4|55|.17
- 长度:34
[02-5][123][257]|[07][0269]+3?$|55
- 长度:142
^[0369]*(([147][0369]*|[258][0369]*[258][0369]*)([147][0369]*[258][0369]*)*([258][0369]*|[147][0369]*[147][0369]*)|[258][0369]*[147][0369]*)*$
- 长度:127
^([0369]|([147]|[258][0369]*[258])([147][0369]*[258]|[0369])*[258]|([258]|[147][0369]*[147])([258][0369]*[147]|[0369])*[147])+$
- 长度:109
^(([0369]|[147][0369]*[258])|([258]|[147][0369]*[147])([0369]|[258][0369]*[147])*([147]|[258][0369]*[258]))*$