打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
学R学初阶_01_R中的正则表达式

本讲主要介绍正则表达式(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)上的意思,而是我们赋予的新的意思,“后设”的意思!

  1. 句号.(period):通配符,可以匹配单个除了换行符(newline)之外的所有字符。

    '.ar' => The car parked in the garage.

  2. 字符集[...](character set):括号中的字符都可以用来匹配,但只能匹配单个位置,如果用短横线-隔开,表示一个范围,例如[a-z],表示从a到z中间所有字符,包括两端。

    '[Tt]he' => The car parked in the garage.

  3. 取反字符集[^...](negated character set):括号中的字符都不可用来匹配。

    '[^c]ar' => The car parked in the garage.

  4. 计数字符+, *, ?(repetition):

    • +:前一个字符重复1次至多次    'c.+t' => The fat cat sat on the mat.

    • *:前一个字符重复0次至多次    '[a-z]*' => The car parked in the garage #21.

    • ?:前一个字符重复0次或1次    '[T]?he' => The car is parked in the garage.

  5. 计数区间{n, m}:和前一条类似,只不过指明了重复次数在n和m之间,包含两端。注意{n}表示只重复n次,{n,}表示重复大于等于n次。

    '[0-9]{2,3}' => The number was 9.9997 but we rounded it off to 10.0.

  6. 亚模式(subpattern):用小括号括起来的字符串属于一个固定的模式,需要不偏不倚的被匹配到。

    '(ar\si)' => The car is parked in the garage. \s代表空格)

  7. 或者|(alternation):或然关系,匹配到其中一个即可。

    '(T|t)he|car' => The car is parked in the garage.

  8. 首尾匹配,^表明后面的字符一定是处于第一的位置,而$表明之前的字符一定是处于最末的位置,见例子:

    '^(T|t)he' => The car is parked in the garage.

    '(at\.)$' => The fat cat. sat. on the mat.

缩写字符集(shorthand character sets)

shorthanddefinition
\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.

  • 向后看(?<>(positive lookbehind):匹配Y后面的X。

    '(?<> => 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函数:”grep”的意思是”global search for regular expression and print matching lines”,这是最核心的函数,其他的几个函数与之大同小异。第一个参数是pattern,即模式;第二个则是input,即需要被匹配的文本。当value=FALSE时,给出匹配上的元素的位置,而value=TRUE则会直接给出匹配上的元素。

> 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函数:”l”在这里是”logical”的缩写,所以,你可以猜到,这个函数给出的结果是一串逻辑值。

> grepl('a+', c('abc', 'def', 'cba a', 'aa'), perl=TRUE)[1] TRUE  FALSE TRUE  TRUE
  • regexpr函数:上面的函数只能告诉你哪些文本被匹配上了,但如果我们需要知道具体匹配的位置,就需要用到regexprgregexpr函数了。除了没有value参数外,regexpr函数的写法与上面完全一样,但得到的结果则是对应于每个input的元素、模式从左至右第一次被匹配到的位置!如果没有匹配上,会显示-1

> regexpr('a+', c('abc', 'def', 'cba a', 'aa'), perl=TRUE)[1]  1 -1  3  1attr(,'match.length')[1]  1 -1  1  2attr(,'useBytes')[1] TRUE

可以看到,函数返回的结果是一个和input等长的vector,其中包含的是首次匹配上的位置,这个vector还含有两个属性(attributes),第一个给出的是匹配上的文本的长度,比如这里第四个,匹配上的是'aa',所以长度是2;第二个暂时不用管它。

  • gregexpr函数:它比上面的兄弟多了一个”g”,你可能已经猜到,这是个global匹配函数,与regexpr唯一的区别是,它返回的值是一个list,针对input中的每个元素,都会给出所有匹配上的位置。

> gregexpr('a+', c('abc', 'def', 'cba a', 'aa'), perl=TRUE)[[1]][1] 1attr(,'match.length')[1] 1attr(,'useBytes')[1] TRUE[[2]][1] -1attr(,'match.length')[1] -1attr(,'useBytes')[1] TRUE[[3]][1] 3 5attr(,'match.length')[1] 1 1attr(,'useBytes')[1] TRUE[[4]][1] 1attr(,'match.length')[1] 2attr(,'useBytes')[1] TRUE
  • regmatches函数:前面的grep函数中,如果设置value=TRUE,那么会给出每个匹配上的元素的整体(比如用”a”去匹配”abc”,grep会得到”abc”),而如果我们需要的仅仅是匹配上的部分(即”a”),regmatches就能派上用场了。这个函数需要的参数一个自然是模式,另一个则是regexprgregexpr返回的值,因为其中包含了匹配上的位置。

> x <- c('abc',="" 'def',="" 'cba="" a',="" 'aa')=""> m <- regexpr('a+',="" x,="" perl="TRUE)"> regmatches(x, m)[1] 'a'  'a'  'aa'> m <- gregexpr('a+',="" x,="" perl="TRUE)"> regmatches(x, m)[[1]][1] 'a'[[2]]character(0)[[3]][1] 'a' 'a'[[4]][1] 'aa'

替换

  • subgsub函数:顾名思义,需要有三个参数,模式、替换文本、原文本(pattern, replacement, input)。区别也很明显,sub是首位匹配,gsub是全局匹配。

> 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'
  • 更为直接的方式是,利用前面提到的regmatches函数,因为它得到刚好仅仅是匹配上的值,那么给这个函数赋予一个vector,里面包含替换文本,即可更改原文本

> x <- c('abc',="" 'def',="" 'cba="" a',="" 'aa')=""> m <- gregexpr('a+',="" x,="" perl="TRUE)"> regmatches(x, m) <- list(c('one'),="" character(0),="" c('two',="" 'three'),="" c('four'))=""> x[1] 'onebc'       'def'         'cbtwo three' 'four'

结语

regex用途非常广泛,但当你初次接触时,会有一段时间的羞涩期,不用担心,这是正常的,很快你就会发现它的好,欲罢不能。regex本身是包含很多深层次内容的,本讲的介绍只是蜻蜓点水、管中窥豹,所以难免挂一漏万,如有错误,还望不吝指正。

要想把regex用得熟、用得好,必须要有一定的训练量!所以最后,给出一些不错的学习资源,望大家继续钻研。

  1. regex-info

  2. regex-online-learning

  3. Regular-Expressions-Cookbook


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#正则表达式快速入门(简介)
现代 ABAP 编程语言中的正则表达式
正则表达式详细介绍
正则表达式来了,Excel中的正则表达式匹配示例
掌握 PHP 中的正则表达式
python中的正则表达式
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服