文章

Regex Golf刷题笔记

零、前言

笔记创建时间:2020/03/10
笔记更新时间:2022/01/07
网站:Regex Golf
答案汇总:Best known Regex Golf solutions

一、Warmup

规律:左边的字符串都包含 foo 子串,右边的字符串都不包含。

答案:

  1. 长度:3
foo

解析:仅匹配包含 foo 子串的字符串。

image.png

  1. 长度:3
f.o

解析:与上一条正则表达式不同的是中间的 o 替换为了任意字符 . ,表示匹配任何包含 f + 一个任意字符 + o 形式子串的字符串。

image.png

二、Anchors

规律:左边的字符串都以 k 结尾,右边的字符串都不以 k 结尾。

答案:

  1. 长度:2
k$

解析:使用结尾符 $ 限定匹配仅结尾为 k 的字符串。

image.png

三、It never ends

规律:左边的字符串都以 u 结尾,右边的字符串都不以 u 结尾。

答案:

  1. 长度:3
u\b

解析:这一题跟上一题规律差不多,但这题规定不能使用结尾符 $ ,所以第一个想到的就是使用单词边界符 \b 来匹配,不过长度相对结尾符 $ 来说就多了一个字符。

image.png

  1. 长度:6
u(?!.)

解析:虽然这条正则表达式没有上一条短,但给出了一个新的思路(使用正向否定预查任意字符来匹配字符串末尾),值得学习。

image.png

四、Ranges

规律:左边的所有字符串都只涉及 af 这几个字母。

答案:

  1. 长度:8
[a-f]{4}

解析:使用 [a-f] 限定匹配字符为 af ,并用 {4} 重复匹配 4 次。

image.png

  1. 长度:8
^[a-f]*$

解析:这一条正则表达式同样使用 [a-f] 限定匹配字符为 af,但改为用 * 重复匹配任意次来将原本的 {4} 缩短了 2 个字符,但任意次(0 次也可以被匹配)也导致右边的字符串都会被匹配上,如果换成 + ,也是只要包含了哪怕一个 af 的字母就会被匹配上。所以首尾加上开始符 ^ 和结束符 $ 来限制所有字符必须均为 af 才能被匹配,到头来正则表达式的长度并没有得到缩减,但也是另一种思路。

image.png

五、Backrefs

规律:左边的字符串都包含 ABC + 任意个字符 + ABC 形式的子串,右边的字符串则没有。

答案:

  1. 长度:9
(...).*\1

解析:用 (...) 来匹配并捕获开头的 ABC 部分,中间的任意个字符则用 .* 匹配,最后结尾的 ABC 使用前面捕获到 \1 来匹配。

image.png

六、Abba

规律:右边的字符串都包含 任意个字符 + ABBA 形式的子串,左边的字符串则没有。

答案:

  1. 长度:17
^(?!.*(.)(.)\2\1)

解析:因为左边为需要匹配到的字符串,而右边为同时不能匹配到的字符串。这次的规律在右边,所以需要排除包含 任意个字符 + ABBA 形式子串的字符串。因此使用正向否定预查 (?!) 来排除。而 ABBA 的部分则用两个 (.) 分别匹配并捕获前面的 AB,并用 \2\1 来匹配后面的 BA 。而所有类型的预查都不消耗字符,所以在开头加上开始符 ^ 。而且并不是所有需要排除的字符串的 ABBA 部分都在开头,所以在 (.)(.)\2\1 的前面加上 .* 来匹配任意个字符。

image.png

  1. 长度:16
^(?!(.)+(.)\2\1)

【重点】
例如表达式: (.)+\1
解释:其中 + 会匹配一次或多次 () 中的部分 . ,而 () 捕获的内容为 + 匹配到的最后一个。

解析:因 (.)+ 组合时, + 会匹配一次或多次 () 中的 .,而 () 捕获到的内容为 + 最后匹配到的内容,所以很巧妙的将长度缩短了 1 个字符。

image.png

  1. 长度:14
^(?!(.)+\1)|ef

解析:这条正则表达式前面的 ^(?!(.)+\1) 部分根据上一条所介绍的原理其实也不难想到,它是将原本的 (.)+(.)\2\1 匹配 任意个字符 + ABBA 形式的子串转变为匹配 任意个字符 + BBA 形式的子串。但左边的 effusivenoisefully 两个字符串由于也包含了 BBA 形式的子串,导致未被匹配到。而细心观察就可以发现,只有这两个字符串中包含 ef 子串,所以用或符号 | 连接 ef 即可。正则表达式被缩短到了 14 个字符。

image.png

七、A man, a plan

规律:左边的字符串都为 回文字符串 ,右边的字符串则没有。

答案:

  1. 长度:14
^(.)(.).*\2\1$

解析:虽然左边的字符串都为 回文字符串 ,但其实可以简化为 AB + 任意个字母 + BA 的形式,所以使用两个 (.) 分别匹配并捕获前面的 AB ,中间的任意个字母则用 .* 匹配,并用 \2\1 来匹配后面的 BA 部分。但为了防止右边的字符串中包含相同形式的子串,所以在开头和结尾分别添加了开始符 ^ 和结尾符 $ 来确保整个字符串都为 AB + 任意个字母 + BA 的形式才会被匹配。

image.png

  1. 长度:13
^(.)[^p].*\1$

解析:这一题其实也可以简化成 A + 任意个字母 + A 的形式。所以可以直接使用 (.).*\1 来匹配,但会发现右边的第 6、9、11、13 条字符串也被匹配到了。所以在开头和结尾分别加上 ^$ 来防止,但右边的还有 sporous 这个字符串被匹配到,但可以发现左边的字符串中没有第二个字母为 p 的,所以使用 [^p] 来排除。

image.png

八、Prime

规律:左边字符串 x 的个数均为素数个,右边则均不为素数个。

答案:

  1. 长度:14
^(?!(..+)\1+$)

解析:使用正向否定预查 (?!) 排除非素数个 x 的字符串。其中 (..+) 可以匹配并捕获 [2,N] 个任意字符,再在后面使用 \1 匹配捕获到的内容,则变为 2[2,N] 个任意字符,然后还有一个 + 跟在 \1 后面,则变为 [2,N][2,N] 个任意字符,最后有一个结尾符 $ 表示直到字符串结尾。

image.png

九、Four

规律:左边的字符串都包含 ABACADA 形式的子串,右边的字符串则没有。

答案:

  1. 长度:12
(.).\1.\1.\1

解析:使用 (.) 匹配并捕获 A ,使用 '\1' 来匹配后续的 A ,其中穿插的 BCD 则用 . 匹配任意字符。

image.png

  1. 长度:11
(.)(.\1){3}

解析:根据上一条正则表达式,可以发现其中 .\1 部分出现了 3 次,所以可以将其合并并用 {3} 来重复匹配 3 次,将正则表达式缩短了 1 个长度。

image.png

十、Order

规律:左边的字符串长度都是 5 或 6,而右边的字符串只有 oriole 的长度是 6 。

答案:

  1. 长度:11
^[^o].{4,5}$

解析:首先根据发现的规律很容易写出 ^.{5,6}$ ,但右边的 oriole 字符串因为是 6 个长度也会被匹配上。进一步可以发现左边的字符串第一个字符都没有 o ,那就好办了,在前面添加 [^o] 来排除第一字符是 o 的情况,那么重复区间同时也要相应的减 1 ,改为 {4,5}

image.png

  1. 长度:11
^.{5}[^e]?$

解析:根据刚总结的规律,右边只有 oriole 字符串因为是 6 个长度也会被 ^.{5,6}$ 匹配上,但可以发现不仅左边字符串第一个字符都没有 o ,而且左边长度为 6 的字符串中第六个字符都没有 e ,所以在后面添加 [^e] 来排除第六个字符是 e 的情况,那么重复区间同时也要相应的减 1 ,改为 {5} 。但左边字符串也有长度为 5 的情况,所以需要在后面添加 ? 表示排除第六个字符为 e 或没有第六个字符。

首先使用 ^.{5} 从字符串的开始匹配 5 个任意字符,然后用 [^e] 排除了

image.png

十一、Triples

规律:左右两边的字符串都是数字,并且左边的都是 3 的倍数,而右边的不是。

答案:

  1. 长度:36
00([369]|1[25]|$)|.1.+4|3.*7.|4.2|55

image.png

  1. 长度:34
00($|3|6|9|12|15)|4.2|.1.+4|55|.17

image.png

  1. 长度:34
[02-5][123][257]|[07][0269]+3?$|55

image.png

  1. 长度:142
^[0369]*(([147][0369]*|[258][0369]*[258][0369]*)([147][0369]*[258][0369]*)*([258][0369]*|[147][0369]*[147][0369]*)|[258][0369]*[147][0369]*)*$

image.png

  1. 长度:127
^([0369]|([147]|[258][0369]*[258])([147][0369]*[258]|[0369])*[258]|([258]|[147][0369]*[147])([258][0369]*[147]|[0369])*[147])+$

image.png

  1. 长度:109
^(([0369]|[147][0369]*[258])|([258]|[147][0369]*[147])([0369]|[258][0369]*[147])*([147]|[258][0369]*[258]))*$

image.png

【扩展】
知乎:正则表达式如何匹配3的倍数?
CSDN:用正则表达式匹配3的任意倍数
简书:用正则表达式匹配3的倍数

License:  CC BY 4.0