正则表达式
在许多应用程序中,我们查找子字符串时并没有被查找模式的完整信息。文本编辑器的用户可能希望仅指定模式的一部分,或是指定某种能够匹配若干个不同单词的模式,或是指定几种可以任意匹配的不同模式。
正则表达式用自然、简单而强大的3种操作组合来描述模式。
使用正则表达式描述模式
模式的描述由3种基本操作和作为操作数的字符组成。这里,我们用语言指代一个字符串的集合(可能是无限的),用模式指代一种语言的详细说明。
连接操作
当我们写出AB时,就指定了一种语言{AB}。它含有一个由两个字符组成的字符串,由A和B连接而成。
或操作
第二个基本操作可以在模式中指定多种可能的匹配。如果我们在两种选择之间指定了一个或运算符,那么它们都将属于同一种语言。我们用竖线符号“|”表示这个操作。例如,A|B指定的语言是{A,B},A|E|I|O|U指定的语言是{A,E,I,O,U}。连接操作的优先级高于或操作,因此AB|BCD指定的语言是{AB,BCD}。
闭包操作
第三种基本操作可以将模式的部分重复任意次数。模式的闭包是由将模式和自身连接任意多次(包括零次)而得到的所有字符串所组成的语言。我们将“*”标记在需要被重复的模式之后,以表示闭包。闭包操作的优先级高于连接操作,因此AB*指定的语言由一个A和0个或多个B的字符串组成,而A*B指定的语言由0个或多个A和一个B的字符串组成。空字符串的记号是 ϵ \epsilon ϵ,它存在于所有文本字符串之中(包括A*)。
括号
我们使用括号来改变默认的优先级顺序。例如,C(AC|B)D指定的语言是{CACD,CBD},(A|C)((B|C)D)指定的语言是{ABD,CBD,ACD,CCD},(AB)*指定的语言是由将AB连接任意多次得到的所有字符串和空字符串组成的{
ϵ
\epsilon
ϵ,AB,ABAB,…}。
正则表达式举例:
正则表达式 | 匹配的字符串 | 不匹配的字符串 |
---|---|---|
(A|B)(C|D) | AC AD BC BD | 其他所有字符串 |
A(B|C)*D | AD ABD ACD ABCCBD | BCD ADD ABCBC |
A*|(A*BA*BA*)* | AAA BBAABB BABAAA | ABA BBB BABBAAA |
缩略写法
一般的应用程序都在基本规则的基础上增加了各种额外的规则,以力求简洁地描述实际应用中所需要地语言。从理论角度来看,它们都只是涉及多个操作数的一系列操作的缩略写法;从实际角度来看,它们是对基本操作的实用扩展,以便能够写出小巧的模式。
字符集描述符
只用一个或几个字符来直接表示一个字符集时常能够带来方便。点“.”是一个能够表示任意字符的通配符。包含在方括号中的一系列字符表示这些字符中的任意一个。这一系列字符可以由一个范围来表示。如果开头字符为“^”,这个方括号表示的就是任意非该括号内的字符。这些记法都是一系列或操作的简写。
字符集描述符
名称 | 记法 | 举例 |
---|---|---|
通配符 | . | A.B |
指定的集合 | 包含在[]中的字符 | [AEIOU]* |
范围集合 | 包含在[]中,由“-”分隔 | [A-Z][0-9] |
补集 | 包含在[]中,首字母为“^” | [^AEIOU]* |
闭包的简写
闭包运算符表示将它的操作数复制任意多次。在实际应用中,我们希望能够灵活指定重复的次数,或者次数的范围。我们用“+”(加号)表示至少复制一次,“?”(问号)表示重复0次或1次。用写在“{}”(花括号)内的数或者范围来指定重复的次数。和刚才一样,这些记法也是一系列基本的连接、或和闭包操作的简写。
闭包的简写(指定操作数的重复次数):
选项 | 记法 | 举例 | 原始写法 | 语言中的字符串 | 不在语言中的字符串 |
---|---|---|---|---|---|
至少重复1次 | + | (AB)+ | (AB)(AB)* | AB ABABAB | ϵ \epsilon ϵ BBBAAA |
重复0或1次 | ? | (AB)? | ϵ \epsilon ϵ|AB | ϵ \epsilon ϵ AB | 所有其他字符串 |
重复指定次数 | 由{}指定次数 | (AB){3} | (AB)(AB)(AB) | ABABAB | 所有其他字符串 |
重复指定范围的次数 | 由{}指定范围 | (AB){1-2} | (AB)|(AB)(AB) | AB ABAB | 所有其他字符串 |
转义序列
某些字符,例如“\”,“.”,“|”,“*”,“(”和“)”,都是用来构造正则表达式的元字符。我们使用以反斜杠开头的转义序列来将元字符和字母表中的字符区别开来。一个转义序列可以是一个“\”加上单个元字符(这就表示这个字符本身)。例如,“\”表示的就是“\”。其他转义序列表示了特殊字符和空白字符。例如,“\t”表示一个制表符,“\n”表示一个换行符,“\s”表示任意空白字符。
正则表达式的实际应用
子字符串查找
我们的总体目标是开发一种算法,能够判定给定子字符串是否包含在给定正则表达式所描述的字符串集合之中。如果文本包含在模式所描述的语言之中,就称文本和模式相匹配。正则表达式的模式匹配一般化了子字符串查找问题。准确地说,要在一段文本txt中查找一个字符串pat,就是检查txt是否存在于模式“.*pat.*”所描述的语言之中。
合法性检查
当你在某个商业网站上输入一个日期或是账号时,输入处理程序会检查输入的格式是否正确。进行这类检查的一种方式是用代码检查所有可能出现的情况。更好的办法是定义一个正则表达式来描述所有合法的输入。之后,检查用户的输入是否合法就完全是模式匹配问题了:输入包含在正则表达式所描述的语言之中吗?一般来说,相比一个能够检查所有情况的程序,正则表达式是对所有有效字符串的集合更加准确和精炼的表达。
正则表达式的典型应用:
应用场景 | 正则表达式 | 匹配 |
---|---|---|
字符串查找 | .*NEEDLE.* | A HAYSTACK NEEDLE IN |
电话号码 | \([0-9]{3}\)\ [0-9]{3}-[0-9]{4} | (800) 867-5309 |
java标识符 | [$_A-Za-z][$_A-Za-z0-9]* | Pattern_Matcher |
基因组 | gcg(cgg|agg)*ctg | gcgaggaggcggcggctg |
电子邮件地址 | [a-z]+@([a-z]+\.)+(edu|com) | rs@cs.princeton.edu |
正则表达式模式匹配的起源是Unix的命令grep,它会打印出和给定正则表达式匹配的所有输入行。例如,某个目录中含有许多.java文件,而你希望知道哪些文件使用了StdIn。这条命令可以很快给出答案:
% grep StdIn *.java
它会打印出每个文件中与“.*StdIn.*”匹配的每一行代码。