本讲主要介绍正则表达式(regular expression,
regex
)的概念、主要语法风格以及在R中的使用方法。
欢迎转载,但请注明出处!
为何讲它
我在学习
The Art of R Programming
第35页的时候遇到一个练习,我想用模式匹配去解决,也就是regex,但遇到了未曾料到的问题,于是我在stackoverflow上提问了,大牛的解答让我想要集中几天精力搞清楚regex到底怎么用,问题见此:
my question
,
不巧被标记了重复
…
regex是一个相对独立的内容,几乎所有编程语言都实现了这种功能,这也证明了它的重要性,
另外,本讲标题序号虽然是01,但大家可以放在遇到实际问题时再来学,那样有了共鸣,印象会更深刻!
正则表达式
我们在面对生物数据,尤其是序列信息(比如碱基序列、氨基酸序列等)的时候, 会时常想要问自己,这其中是否包含着且含有多少某种已知的
模式
,一段DNA中是否包含转录起始特征
TATA box
、一段RNA中是否包含某种
lncRNA
、一段肽链中是否包含
锌指结构
等等;另一方面,我们在操作数据时,会时常遇到诸如把某个字符(对象)换成另一种字符(对象)的
替换操作
,而其本质还是如何搜索符合某种(替换)
模式
的对象。
在这些几乎天天都可以碰到的模式匹配/搜索问题中,正则表达式就是一把解决问题的利剑!
正则表达式:它是一连串用来
描述模式
的字符,它的书写遵循一定的语法规则,当用户进行文本匹配的时候,程序语言会调用预设的引擎(其实就是内置的或第三方库里的软件包),利用正则表达式去搜索符合模式的文本信息!英文名是regular expression,常缩写为regex,或regexp。
知道英文名后,大家就应该不用去纠结“正则”这个反人类名词了,你可以理解为规则的、规范的,或者直接叫它“模式表达式”。
多说一句,劝大家学习时尽量阅读英文材料,中文材料中很多硬生生的翻译,不仅不能达意,还会让很多人产生误解!
语法风格
由以上概念,我们大概会以为正则表达式就像一种模式书写规范,它应该是唯一的,且被大多数编程语言所共用。然而,事情却不那么优雅,从regex诞生之日起,先后产生了数十种适应于不同程序语言的语法规则和风格,而且有些语言还实现了不只一种语法规则,比如R。其中流传最广的一种是:POSIX (Portable Operating System Interface for uniX)标准下的BRE (Basic Regular Expression)、ERE (Extended Regular Expression);另一种则是PCRE (Perl Compatible Regular Expressions),详情请参考
regular expression in wiki
。
在R语言(版本>R 2.10.0)中,它使用了两种语法规则:默认的是
Ville Laurikari’s TRE
,还有一种就是
PCRE
。
在本讲中,我使用的是高效且被广泛使用的PCRE语法,同样也推荐大家使用,操作很简单,只需在相应的R函数里加上
perl = TRUE
参数即可调用此语法,在后面的内容中大家会看到!
常用语法
虽然说regex有很多种语法风格,但毕竟人是比较懒的,既然有前人的轮子在,而且用起来也很不错,那么就不会有人愿意从头再造一遍,所以这些语法大同小异,下面我将介绍一些
PCRE中常用的语法规则
。(以下内容多数引用自
learn regex the easy way
)
基本匹配
“the” => The fat cat sat on
the
mat.
“The” =>
The
fat cat sat on the mat.
(默认大小写敏感)
元字符(meta characters)
所谓的meta-X(
中文翻译成“元”啊,“宏”啊,“后设”啊,先可以不管
),在英文中代表概念X背后的概念,而每个meta词汇又有其特有的解释方法,比如data是数据(如工资表),meta-data则是描述这个工资表的数据(什么纸打印的,用的哪种墨盒,存放在哪个档案库等等)。此处的meta characters,指的是这些字符虽然本质是字符,但并非是它们字面(literal)上的意思,而是我们赋予的新的意思,“后设”的意思!
-
句号
.
(period):通配符,可以匹配
单个
除了换行符(newline)之外的所有字符。
".ar"
=> The
car
par
ked in the
gar
age.
-
字符集
[...]
(character set):括号中的字符都可以用来匹配,但只能匹配
单个
位置,
如果用短横线
-
隔开,表示一个范围,例如[a-z],表示从a到z中间所有字符,包括两端。
"[Tt]he"
=>
The
car parked in
the
garage.
-
取反字符集
[^...]
(negated character set):括号中的字符
都不
可用来匹配。
"[^c]ar"
=> The car
par
ked in the
gar
age.
-
计数字符
+
,
*
,
?
(repetition):
-
+
:前一个字符重复1次至多次
"c.+t"
=> The fat
cat sat on the mat
.
-
*
:前一个字符重复0次至多次
"[a-z]*"
=> T
he car parked in the garage
#21.
-
?
:前一个字符重复0次或1次
"[T]?he"
=>
The
car is parked in t
he
garage.
计数区间
{n, m}
:和前一条类似,只不过指明了重复次数在n和m之间,包含两端。注意
{n}
表示只重复n次,
{n,}
表示重复大于等于n次。
"[0-9]{2,3}"
=> The number was 9.
999
7 but we rounded it off to
10
.0.
亚模式(subpattern):用小括号括起来的字符串属于一个固定的模式,需要不偏不倚的被匹配到。
"(ar\si)"
=> The c
ar i
s parked in the garage.
(
\s
代表空格)
或者
|
(alternation):或然关系,匹配到其中一个即可。
"(T|t)he|car"
=>
The car
is parked in
the
garage.
首尾匹配,
^
表明后面的字符一定是处于第一的位置,而
$
表明之前的字符一定是处于最末的位置,见例子:
"^(T|t)he"
=>
The
car is parked in the garage.
"(at\.)$"
=> The fat cat. sat. on the m
at.
缩写字符集(shorthand character sets)
shorthand
|
definition
|
\w
|
匹配所有大小写字母及数字,即
[a-zA-Z0-9]
|
\W
|
与上面相反,即
[^\w]
|
\d
|
匹配所有数字,即
[0-9]
|
\D
|
与上面相反,即
[^\d]
|
\s
|
匹配所有空格字符,如
[\t\n\r]
|
\S
|
与上面相反,即
[^\s]
|
前后看(lookaround)
这个语法也就是开头提到的、那个让我解决不了的问题的核心,其实学会了它后,你会感觉很有意思。
假设一下,如果我们想找的模式X周围还有特定的亚模式Y,该怎么办?你可以直接匹配你要的模式X,但如果有些X旁边没有Y,我们该怎么排除出去呢?
这时候“前后看”就派上大用场了,我们以Y为眼睛,让它看看四周有没有X,有的话就直接匹配上!
-
向前看
X(?=Y)
(positive lookahead):顾名思义,是匹配Y前面的X。
"(T|t)he(?=\sfat)"
=>
The
fat cat sat on the mat.
-
向前删
X(?!Y)
(negative lookahead):和上面相反,是
不
匹配Y前面的X。
"(T|t)he(?!\sfat)"
=> The fat cat sat on
the
mat.
-
向后看
(?<=Y)X
(positive lookbehind):匹配Y后面的X。
"(?<=(T|t)he\s)(fat|mat)"
=> The
fat
cat sat on the
mat
.
-
向后删
(?(negative lookbehind):
不
匹配Y后面的X。
"(? => The cat sat on
cat
.
有一点要注意的是,lookaround中的模式,即
上面的模式Y,是不会被引擎捕获到的
,这一点在某些特定情况下还蛮有用的,请参看
my question
。
PCRE本身的语法还有不少,但常用的基本都在这里了,如果想深入了解,请参考
PCRE syntax
。
R中怎么使用
前面提到,R实现了两种regex语法风格,个人推荐使用PCRE风格,不然前面的也白看了不是?R中常用的模式匹配操作,一是运用
base
包中的几个函数,二是利用
stringr
包中的系列函数,本文只介绍前者,在熟练之后,大家应该去学习下后者,大神出品,方便易用,参见
R for data science - Strings
。(以下内容多数引用自
Regular Expressions with The R Language
)
匹配
> grep("a+", c("abc", "def", "cba a", "aa"), perl=TRUE, value=FALSE)
[1] 1 3 4
> grep("a+", c("abc", "def", "cba a", "aa"), perl=TRUE, value=TRUE)
[1] "abc" "cba a" "aa"
> grepl("a+", c("abc", "def", "cba a", "aa"), perl=TRUE)
[1] TRUE FALSE TRUE TRUE
> regexpr("a+", c("abc", "def", "cba a", "aa"), perl=TRUE)
[1] 1 -1 3 1
attr(,"match.length")
[1] 1 -1 1 2
attr(,"useBytes")
[1] TRUE
可以看到,函数返回的结果是一个和input等长的vector,其中包含的是
首次
匹配上的位置,这个vector还含有两个属性(attributes),第一个给出的是匹配上的文本的长度,比如这里第四个,匹配上的是
"aa"
,所以长度是2;第二个暂时不用管它。
> gregexpr("a+", c("abc", "def", "cba a", "aa"), perl=TRUE)
[[1]]
[1] 1
attr(,"match.length")
[1] 1
attr(,"useBytes")
[1] TRUE
[[2]]
[1] -1
attr(,"match.length")
[1] -1
attr(,"useBytes")
[1] TRUE
[[3]]
[1] 3 5
attr(,"match.length")
[1] 1 1
attr(,"useBytes")
[1] TRUE
[[4]]
[1] 1
attr(,"match.length")
[1] 2
attr(,"useBytes")
[1] TRUE
> x m regmatches(x, m)
[1] "a" "a" "aa"
> m regmatches(x, m)
[[1]]
[1] "a"
[[2]]
character(0)
[[3]]
[1] "a" "a"
[[4]]
[1] "aa"
替换
> sub("(a+)", "z", c("abc", "def", "cba a", "aa"), perl=TRUE)
[1] "zbc" "def" "cbz a" "z"
> gsub("(a+)", "z", c("abc", "def", "cba a", "aa"), perl=TRUE)
[1] "zbc" "def" "cbz z" "z"
> x m regmatches(x, m) x
[1] "onebc" "def" "cbtwo three" "four"
结语
regex用途非常广泛,但当你初次接触时,会有一段时间的羞涩期,不用担心,这是正常的,很快你就会发现它的好,欲罢不能。regex本身是包含很多深层次内容的,本讲的介绍只是蜻蜓点水、管中窥豹,所以难免挂一漏万,如有错误,还望不吝指正。
要想把regex用得熟、用得好,必须要有一定的训练量
!所以最后,给出一些不错的学习资源,望大家继续钻研。
-
regex-info
-
regex-online-learning
-
Regular-Expressions-Cookbook
阅读
蓝色链接
内容请点击
阅读原文
!
阅读
蓝色链接
内容请点击
阅读原文
!
阅读
蓝色链接
内容请点击
阅读原文
!