Regex Golf网址:https://alf.nu/RegexGolf
Regex Golf通关解答:Regex Golf通关解答-CSDN博客
我对Regex Golf上的题目按个人兴趣做了一个简单的分类,其中基础篇是我认为学习正则表达式最实用的题目。这些题目没有过分的脑洞,它会引导我们去发现那些不常用的正则技巧,去学习那些平日被我们忽视了的正则知识,进而让我们对正则表达式应用有更全面深入的理解。
Ranges–The test vectors were generated by grepping /usr/dict/words. Can you tell?
Ranges一题的匹配规律还是比较容易发现的,题目ranges(范围)的提示也非常明显,左侧待匹配的文本全都只由abcdef这几个字符组成。因此,我们要限定的是字符串所使用字符的范围。正则表达式中匹配字符范围的方法是[],本题的正确答案为:^[a-f]+$。需要说明的是:
(1)[]范围中的大部分特殊字符代表匹配本身,无需转义。例如[.*$],就代表匹配字符“.”“*”或者“$”。只有字符“]”和字符“\”需要转义,即[\\]匹配字符“\”,[\]]匹配字符“]”;
(2)[]范围中第一个字符为“^”时,表示只匹配不在[]范围之内的字符。例如[^abc],表示匹配除abc之外的其他字符;但当“^”字符出现在[]范围中其他位置时,则匹配“^”字符本身,例如[abc^];如果只想匹配“^”一个字符,则需要进行转义[\^];
(3)[]范围中可以使用匹配字符类型的元字符,例如[\S],匹配所有非空字符;[\d],匹配数字字符。
Backrefs–This doesn't really work as a tutorial. Not really clear what you're supposed to do here.
Backrefs也是一道提示非常明显的题目。经观察,很容易发现左侧待匹配文本中都出现了某一模式在文本串中重复出现两次的规律。例如allochirally中的all,anticovenanting中的ant,等等。匹配这样的模式,所用到的技巧叫做“反向引用”,也就是我们的题面backrefs了。
反向引用是指在正则表达式中使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。反向引用非常适合用来重复搜索前面某个分组匹配的文本。
例如我们要匹配形如aa,bb,cc这样两个相同字母的模式,我们就可以使用正则表达式:(.)\1。(.)匹配第一个字符并捕获第一分组,然后使用\1对分组一进行重复,就得到了aa这样的匹配模式。
回到本题目,我们发现前后都是三字符的重复,因此先用(...)来匹配并捕获第一组字符,在用\1来进行重复。由于不清楚模式具体重复的位置,因此使用.*来匹配任意长度。本题的完整答案为:(...).*\1。
在反向引用中,如果不希望对()中的内容进行捕获并分配组号的话,可以使用语法(?:);而在捕获分组时,除了使用自动分配的组号外,我们也可以为其分配组名,其语法格式为(?<groupName>...),并使用\k<groupName>来引用这个分组。按照这种方法,本题目的答案也可写为(?<name>...).*\k<name>。
Four–You can get an extra point by ignoring the name of this level.
在了解了反向引用以后,Four这一题简直是手到擒来:(.).\1.\1.\1。不过有意思的这一题的提示语“You can get an extra point by ignoring the name of this level”,这其实就是这个网站现在推崇的主流玩法了,即不考虑真实的模式和规律,只考虑如何用最少的字符来完成页面上的用例匹配。本题目前字符最少的答案为:(.).\1...\1。
Abba–Let's pretend this one is not a rehash of the last one.
乍一看题目,可能还以为是让我们去匹配包含abba模式的文本串呢。学习了反向引用,这个自然是非常容易:(.)(.)\2\1。但仔细观察会发现,包含abba模式的文本全在右侧不能匹配的文本列表里。也就是说,我们要匹配的不是包含abba模式的文本,而是不包含abba模式的文本。那么这里就要用到正则表达式里另一个重要的技巧“零宽断言”了。
零宽断言的“零宽”和“断言”可以分开理解。“零宽”是指这个匹配没有宽度,不消耗字符,所以它匹配的是一个位置;而“断言”则是对这个位置特点的描述。也就是说,“零宽断言”是在匹配一个具备某种特点的位置。共有四种零宽断言方式:
(1)(?=pattern):零宽度正预测先行断言,它断言此位置后面必须匹配pattern模式的文本。例如:^(?=re),它匹配由re开头的文本,包括read, regress, repeat等等。
(2)(?!pattern):零宽度负预测先行断言,它断言此位置的后面不能匹配pattern模式的文本。例如:a(?!b),它匹配后续不是字符“b”的字符“a”,例如ac,ad,ae,但无法匹配ab。
(3)(?<=pattern):零宽度正回顾后发断言,它断言此位置的前面必须pattern模式的文本。例如:(?<=k)$,它匹配由k结尾的文本,包括kick,pick,stick等等。
(4)(?<!pattern):零宽度负回顾后发断言,它来断言此位置的前面不能匹配pattern模式的文本。例如(?<!a)b,它匹配前面不是字符“a”的字符“b”,例如bb,cb,db,但无法匹配ab。
回到题目本身,“匹配不包含abba模式的文本”的意思可以描述为“匹配起始位置,该位置后不能匹配包含abba模式的文本串”,应用零宽度负预测先行断言,本题答案为:^(?!.*(.)(.)\2\1)。目前字符最少的答案为:^(?!(.)+\1)|fu。