专栏名称: 生信媛
生信媛,从1人分享,到8人同行。坚持分享生信入门方法与课程,持续记录生信相关的分析pipeline, python和R在生物信息学中的利用。内容涵盖服务器使用、基因组转录组分析以及群体遗传。
目录
相关文章推荐
51好读  ›  专栏  ›  生信媛

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

生信媛  · 公众号  · 生物  · 2018-03-09 09:00

正文

请到「今天看啥」查看全文


本讲主要介绍正则表达式(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 par ked in the gar age.

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

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

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

    "[^c]ar" => The car par ked in the gar age.

  4. 计数字符 + , * , ? (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 函数:”grep”的意思是” g lobal search for r egular e xpression and p rint 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 函数:上面的函数只能告诉你哪些文本被匹配上了,但如果我们需要知道具体匹配的位置,就需要用到 regexpr gregexpr 函数了。除了没有 value 参数外, regexpr 函数的写法与上面完全一样,但得到的结果则是对应于每个input的元素、模式从左至右 第一次 被匹配到的位置!如果没有匹配上,会显示 -1

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

    > 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
    • regmatches 函数:前面的 grep 函数中,如果设置 value=TRUE ,那么会给出每个匹配上的元素的整体(比如用”a”去匹配”abc”, grep 会得到”abc”),而如果我们需要的仅仅是匹配上的部分(即”a”), regmatches 就能派上用场了。 这个函数需要的参数一个自然是 模式 ,另一个 则是 regexpr gregexpr 返回的值 ,因为其中包含了匹配上的位置。

    > 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 gsub 函数:顾名思义,需要有三个参数, 模式、替换文本、原文本(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  m  regmatches(x, m)  x
    [1] "onebc"       "def"         "cbtwo three" "four"

    结语

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

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

    1. regex-info

    2. regex-online-learning

    3. Regular-Expressions-Cookbook


    阅读 蓝色链接 内容请点击 阅读原文

    阅读 蓝色链接 内容请点击 阅读原文

    阅读 蓝色链接 内容请点击 阅读原文









    请到「今天看啥」查看全文