再谈正则表达式

CSP_Sept

2020-08-02 11:47:09

Personal

## -1 为什么要再谈 虽然之前有一篇日报讲正则表达式,但是内容比较深奥且排版不美观,于是决定“再谈”一次。 ## 0 引言:为什么要学习正则表达式 **Regex Match Tracer 安装** 链接: <https://pan.baidu.com/s/13_JR3vzBJw97aP7tQx_6ng> 提取码: `lgrb` Regex Match Tracer 可以检验正则表达式的正确性。 >在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。**正则表达式**就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 使用正则表达式可以用来**按一定的方式处理某些字符串**。 >正则表达式是一种查找以及字符串替换操作。正则表达式在文本编辑器中广泛使用,比如正则表达式被用于: > >- 检查文本中是否含有指定的特征词 >- 找出文中匹配特征词的位置 >- 从文本中提取信息,比如:字符串的子串 >- 修改文本 > >与文本编辑器相似,几乎所有的高级编程语言都支持正则表达式。在这样的语境下,“文本”也就是一个字符串,可以执行的操作都是类似的。一些编程语言(比如 Perl,JavaScript)会检查正则表达式的语法。 ## 1 入门 如果你想在一段文本中查找 $\tt hi$,你可以使用正则表达式 `hi`。但是,这样的话,$\tt history,high,\cdots$ 中的 `hi` 都会被查到,如果你想精确地查询 $\tt hi$ 这个**单词**,你可以用正则表达式 `\bhi\b`,这里的 `\b` 就是正则表达式的一个**特殊字符**(元字符),代表「单词的分界处」。 ### 1.1 元字符 [由于某些原因,你不得不在云剪贴板查看此内容 /kel](/paste/t34zr2ij) ### 1.2 限定符 - `*` :重复 $0$ 次或更多次。 - `+` :重复 $1$ 次或更多次。 - `?` :重复 $0$ 次或 $1$ 次。 - `{n}` :重复 $n$ 次。 - `{n,}` :重复 $n$ 次或更多次。 - `{n,m}` :重复 $n$ 到 $m$ 次。 ### 1.3 字符集合 `[123]` 代表匹配 $\tt 1,2,3$ 中的任意一个,也可以使用如 `[0-9]`,这个意义是同 `\d` 一样的。 ### 1.4 反义 - `\W` :匹配任意不是字母,数字,下划线,汉字的字符。 - `\S` :匹配任意不是空白符的字符。 - `\D` :匹配任意非数字的字符。 - `\B` :匹配不是单词开头或结束的位置。 - `[^123]` :匹配除了 $\tt 1,2,3$ 以外的任何字符。 ## 2 进阶 ### 2.1 后向引用 使用 `()` 框住一个子表达式后,该表达式会有一个编号(从 $1$ 开始),之后,可以使用 `\组号` 的形式重复编号为 `组号` 的子表达式。 - `(exp)` :匹配 `exp`,并自动分组。 - `(?<Name>exp)` :匹配 `exp`,并命名为 `Name`,重复可用 `\k<Name>`。 - `(?:exp)` :匹配 `exp`,但不捕获匹配的文本,也不给此分组分配组号。 - `(?=exp)` :匹配 `exp` 前面的位置。 - `(?<=exp)` :匹配 `exp` 后面的位置。 - `(?!exp)` :匹配 `exp` 后面的不是 `exp` 的位置。 - `(?<!exp)` :匹配 `exp` 前面的不是 `exp` 的位置。 - `(?#note)` :这种类型的分组**不**对正则表达式的处理产生任何影响,用于提供注释让人阅读。 - `\n` :重复组号为 $n$ 的子表达式。 好的,我们来解释一下:比如 `\b\w+(?=ing)` 会匹配一个单词中 $\tt ing$ 前面的部分,在 `He is playing computer and coding. ` 中会匹配到 $\tt play,cod$。 此外,对于 `\n` 的应用,我也举个例子:匹配四个相同的英语单词,可以使用以下代码:`\b([a-z]+) \1 \1 \1\b`。其中 `\1` 指的是子表达式 `[a-z]+`。 ### 2.2 实践 请匹配一个**合法邮箱**,合法邮箱满足: - 第一部分,由若干个字符组成(包括 `a-z,A-Z,0-9`)。 - 第二部分,由一个 `@` 组成。 - 第三部分,由网站名组成,网站名由若干个字符组成(包括 `a-z,A-Z,0-9`),后缀可用 `.com`。 示例如:`\b([a-zA-Z0-9]+)@\1\.com\b` (还少了字符串匹配中的哪两个元字符?) 当然,这只是为了简便写的,它只能匹配类似于 `[email protected]` 这样的网站名与邮箱名相同的邮箱。 ### 2.3 贪婪 正则表达式会匹配**尽可能多**的字符,如 `a.*b` 就会匹配一个以 $\tt a$ 开头、$\tt b$ 结尾的最长的字符串。当然,如果你想匹配**尽可能少**的字符,在后面加上一个 `?` 即可。 ### 2.4 栈 匹配一个字符串,问 `<>` 是否匹配完整(是否是一个完整的括号序列) 需要语法: - `(?'Name')` :把捕获的内容命名为 `Name`,并压入栈($\bf{Stack}$)。 - `(?'-Name')` :从栈中压出最后一个压入的名为 `Name` 的内容,若不存在,捕获失败。 - `(?(Name)Yes|No)` :若栈中有一个名为 `Name` 的内容,则执行 `Yes`,否则执行 `No`。 - `(?!)` :**直接**断言无法匹配(可以类比为 C++ 中的 `break`)。 于是正则表达式: ```plain < # 匹配最左边的括号 [^<>]* # 匹配非括号部分 ( ( (?'Stack'<) # 如果匹配到左括号, 压入 "Stack" [^<>]* # 匹配非括号部分 )+ ( (?'-Stack'>) # 如果匹配到右括号, 弹出 "Stack" [^<>]* # 匹配非括号部分 )+ )* (?(Stack)(?!)) > ``` ## 3 参考资料 - 3.1 [正则表达式30分钟入门教程(By deerchao)](https://deerchao.cn/tutorials/regex/regex.htm) - 3.2 [正则表达式 - 微软](https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/regular-expressions) - 3.3 [Github 的文档](https://github.com/EZLippi/practical-programming-books/blob/master/src/30-minutes-to-learn-regex.md)