第 7 章 正则表达式

7.1 什么是正则表达式

我们在word文档或者excel中,经常使用查找和替换, 然而有些情况,word是解决不了的,比如

  • 条件搜索
    • 统计文中,前面有 “data”, “computer” or “statistical” 的 “analysis”,这个单词的个数
    • 找出文中重复的单词,比如“we love love you”
  • 拼写检查
    • 电话号码(邮件,密码等)是否正确格式
    • 日期书写的规范与统一
  • 提取信息
    • 提取文本特定位置的数据
  • 文本挖掘
    • 非结构化的提取成结构化

这个时候就需要用到正则表达式(Regular Expression),这一强大、便捷、高效的文本处理工具。那么,什么是正则表达式呢?简单点说,正则表达式是处理字符串的。复杂点说,正则表达式描述了一种字符串匹配的模式(pattern),通常被用来检索、替换那些符合某个模式(规则)的文本。这种固定的格式的文本,生活中常见的有电话号码、网络地址、邮件地址和日期格式等等。

正则表达式并不是R语言特有的,事实上,几乎所有程序语言都支持正则表达式。

R语言中的字符串操作,基础包里有很多函数可以使用,然而大神Hadley Wickham开发的stringr包让正则表达式简单易懂,所以今天我们介绍这个包,本章的内容与《R for data science》第10章基本一致。本章目的教大家写简单的正则表示式就行了。

7.2 字符串基础

7.2.1 字符串长度

想获取字符串的长度,可以使用str_length()函数

## [1] 18

对字符串向量

## [1]  1 18 NA

数据框里配合dplyr函数,同样很方便

7.2.2 字符串组合

把字符串拼接在一起

## [1] "xy"

把字符串拼接在一起,可以设置中间的间隔

## [1] "x, y"
## [1] "x" "y" "z"

是不是和你想象的不一样,那就?str_c,或者试试这个

## [1] "x, x" "y, y" "z, z"

用在数据框里

使用collapse选项,是先组合,然后再转换成单个字符串,大家对比下

## [1] "x|a" "y|b" "z|c"
## [1] "xa|yb|zc"

7.2.3 字符串取子集

截取字符串的一部分,需要指定截取的开始位置和结束位置

## [1] "App" "Ban" "Pea"

开始位置和结束位置如果是负整数,就表示位置是从后往前数,比如下面这段代码,截取倒数第3个至倒数第1个位置上的字符串

## [1] "ple" "ana" "ear"

也可以进行赋值,如果该位置上有字符,就用新的字符替换旧的字符

## [1] "Qpple"  "Qanana" "Qear"

7.3 使用正则表达式进行模式匹配

正则表示式慢慢会呈现了

7.3.1 基础匹配

str_view() 是查看string是否匹配pattern,如果匹配,就高亮显示

有时候,我们希望在字符串中,a前后都有字符(a处在两字符中间, rap, bad, sad, wave,spear等等)

这里的. 代表任意字符。如果向表达.本身呢?

7.3.2 锚点

希望a是字符串的开始

希望a是一字符串的末尾

7.3.3 字符类与字符选项

前面提到,.匹配任意字符,事实上还有很多这种特殊含义的字符:

  • \d: matches any digit.
  • \s: matches any whitespace (e.g. space, tab, newline).
  • [abc]: matches a, b, or c.
  • [^abc]: matches anything except a, b, or c.

7.3.4 重复

控制匹配次数:

  • ?: 0 or 1
  • +: 1 or more
  • *: 0 or more

控制匹配次数:

  • {n}: exactly n
  • {n,}: n or more
  • {,m}: at most m
  • {n,m}: between n and m
  • 默认的情况,*, + 匹配都是贪婪的,也就是它会尽可能的匹配更多
  • 如果想让它不贪婪,而是变得懒惰起来,可以在*, + 加个?

小结一下呢

7.3.5 分组与回溯引用

##  [1] "apple"        "apricot"      "avocado"     
##  [4] "banana"       "bell pepper"  "bilberry"    
##  [7] "blackberry"   "blackcurrant" "blood orange"
## [10] "blueberry"

我们想看看这些单词里,有哪些字母是重复两次的,比如aa, pp. 如果用上面学的方法

发现不是和我们的预想不一样呢。

所以需要用到新技术 分组与回溯引用

  • . 是匹配任何字符
  • (.) 将匹配项括起来,它就用了一个名字,叫\\1; 如果有两个括号,就叫\\1\\2
  • \\1 表示回溯引用,表示引用\\1对于的(.)

所以(.)\\1的意思就是,匹配到了字符,后面还希望有个同样的字符

如果是匹配abab, wcwc

如果是匹配abba, wccw呢?

是不是很神奇?

7.4 解决实际问题

7.4.1 确定一个字符向量是否匹配一种模式

实际问题中,我们希望得到,是否匹配?或者将匹配的筛选处理, 这个时候,需要用到str_detect等函数

## [1]  TRUE FALSE  TRUE

stringr::words包含了牛津字典里常用单词

## [1] "a"        "able"     "about"    "absolute"
## [5] "accept"   "account"

我们统计下以t开头的单词,有多少个?

## [1] 65

我们又一次看到了强制转换.

以元音结尾的单词,占比多少?

## [1] 0.2765

放在数据框里看看, 看看以x结尾的单词是哪些?

str_detect() 有一个功能类似的函数str_count(),区别在于,后者不是简单地返回是或否,而是返回字符串中匹配的数量

## [1] 1 3 1

7.4.2 确定匹配的位置

大家放心,正则表达式不会重叠匹配。比如用"aba"去匹配"abababa",肉眼感觉是三次,但正则表达式告诉我们是两次,因为不会重叠匹配

## [1] 2

7.4.3 提取匹配的内容

## [1] "red|orange|yellow|green|blue|purple"

colour_match 这里是一个字符串,放在pattern参数位置上也是正则表达式了,

这里注意以下两者的区别

## [1] "blue"
## [[1]]
## [1] "blue" "red"
## [1] "It is hard to erase blue or red ink."          
## [2] "The green light in the brown box flickered."   
## [3] "The sky in the west is tinged with orange red."

取出sentences中,含有有两种和两种颜色以上的句子。不过,不喜欢这种写法,看着费劲,还是用tidyverse的方法

str_extract()提取匹配, 谁先匹配就提取谁

str_extract_all()提取全部匹配项

7.4.4 替换匹配内容

只替换匹配的第一项

## [1] "-pple"  "p-ar"   "b-nana"

替换全部匹配项

## [1] "-ppl-"  "p--r"   "b-n-n-"

7.4.5 拆分字符串

这个和str_c()是相反的操作

## [1] "I love my country"
## [[1]]
## [1] "I"       "love"    "my"      "country"
##      [,1]      [,2]    
## [1,] "Name"    "Hadley"
## [2,] "Country" "NZ"    
## [3,] "Age"     "35"

7.5 进阶部分

带有条件的匹配

7.5.1 look ahead

想匹配Windows,同时希望Windows右侧是"95", "98", "NT", "2000"中的一个

Windows后面的 () 是匹配条件,事实上,有四种情形:

  • (?=pattern) 要求此位置的后面必须匹配表达式pattern
  • (?!pattern) 要求此位置的后面不能匹配表达式pattern
  • (?<=pattern) 要求此位置的前面必须匹配表达式pattern
  • (?<!pattern) 要求此位置的前面不能匹配表达式pattern

注意:对于正则表达式引擎来说,它是从文本头部向尾部(从左到右)开始解析的,因此对于文本尾部方向,称为“前”,因为这个时候,正则引擎还没走到那块;而对文本头部方向,则称为“后”,因为正则引擎已经走过了那一块地方。

7.5.2 look behind

7.6 案例分析

7.6.1 案例1

我们希望能提取第二列中的数值,构成新的一列

7.6.2 案例2

提取第二列中的大写字母

7.6.3 案例3

要求:中英文分开

7.6.4 案例4

要求:提取起始数字

7.6.5 案例5

要求:提取大写字母后的数字

思考题,

  • 如何提取大写字母后的连续数字,比如B217C后面的217
  • 如何提取提取数字前的大写字母?
  • 为什么第一个正则表达式返回结果为""
## [[1]]
##       [,1]
##  [1,] ""  
##  [2,] ""  
##  [3,] ""  
##  [4,] ""  
##  [5,] ""  
##  [6,] ""  
##  [7,] ""  
##  [8,] ""  
##  [9,] ""  
## [10,] ""  
## [11,] ""  
## [12,] ""  
## [13,] ""  
## [14,] ""  
## [15,] ""  
## [16,] ""  
## [17,] ""  
## [18,] ""  
## [19,] "C" 
## [20,] "C" 
## [21,] "C" 
## [22,] ""  
## [23,] ""  
## [24,] ""  
## [25,] ""  
## [26,] ""  
## [27,] ""  
## [28,] ""  
## [29,] ""  
## [30,] ""
## [[1]]
##      [,1]
## [1,] "CC"
## [2,] "C"

7.6.6 案例6

提取数字并求和