正則表達(dá)式
正則表達(dá)式是對(duì)字符串類型數(shù)據(jù)進(jìn)行匹配判斷,提取等操作的一套邏輯公式。
處理字符串類型數(shù)據(jù)方面,高效的工具有Perl和Python。如果我們只是偶爾接觸文本處理任務(wù),則學(xué)習(xí)Perl無疑成本太高;如果常用Python,則可以利用成熟的正則表達(dá)式模塊:re
庫; 如果常用R,則使用Hadley大神開發(fā)的stringr
包則已經(jīng)能夠游刃有余。
“正則表達(dá)式是描述一組字符串特征的模式,用來匹配特定的字符串?!?br style="margin: 0px; padding: 0px;">—— Ken Thompson
上面的這句話已經(jīng)說明了什么是正則表達(dá)式。我把正則表達(dá)式看作一組有特殊語法的字符串,用來方便地從文本中匹配想要獲得的內(nèi)容。
R支持三種正則表達(dá)式模式:
1.默認(rèn)的是POSIX 1003.2 extended regular expressions。
2.通過設(shè)置參數(shù)perl=TRUE的方式使用Perl-like regular expressions。
3.通過設(shè)置參數(shù)fixed=TRUE的方式把正則表達(dá)式中的字符串視為其字面含義匹配。
如果已經(jīng)熟悉Perl語言的正則表達(dá)式,則可以跳過這一節(jié),直接了解R所提供的一些正則表達(dá)式的函數(shù)即可。
以下字符在正則表達(dá)式中不是其字面含義,是有特殊含義的:
\ | ( ) [ ] { } ^ $ * + ? .
要想在正則表達(dá)式中使用這些特殊字符的字面含義,需要在其前面加轉(zhuǎn)義符“\\”,例如想要匹配左側(cè)圓括號(hào)則使用“\\(”。需要注意的是,R中的正則表達(dá)式的轉(zhuǎn)義符需要兩個(gè)。
字符 含義
. 匹配除換行符 \n 之外的任何單個(gè)字符
- 連字符 當(dāng)且僅當(dāng)在字符組[]的內(nèi)部表示一個(gè)范圍,比如[A-Z]
| 或(or)
( ) 表示一個(gè)字符組,括號(hào)內(nèi)的字符串將作為一個(gè)整體被匹配
[ ] 括號(hào)內(nèi)的任意字符將被匹配
{ } 包含指示出現(xiàn)次數(shù)上下限的數(shù)值
/ 正則表達(dá)式模式的開始或結(jié)尾
\ 轉(zhuǎn)義符
字符 | 含義 |
---|---|
^ | 字符串的開頭 |
$ | 字符串的結(jié)尾 |
\\b | 英文單詞的邊界(perl模式) |
\\B | 非英文單詞的邊界(perl模式) |
量詞放在字符的后面,來控制前面字符出現(xiàn)的次數(shù)。
字符 | 含義 |
---|---|
* | 匹配0次或更多次 {0,} |
+ | 匹配1次或更多次 {1,} |
? | 匹配0次或1次 {0,1} |
{N} | 匹配N次 |
{N,} | 至少匹配N次 |
{N,M} | 至少匹配N次,但不超過M次 |
默認(rèn)的情況下,這些量詞都是貪婪的,它們試圖匹配盡可能多的字符。要想讓它們匹配盡可能少的字符,則可以在量詞后加”?”來實(shí)現(xiàn)。舉例來說,在正則表達(dá)式“e+”,會(huì)匹配到字符串“beef”中的“ee”,而“e+?”,會(huì)分別匹配到其中的兩個(gè)“e”,作為兩次獨(dú)立的匹配。
字符類表示一類字符的集合,如“[abc]”表示a或b或c,使用“[]”把一些字符括起來代表其中的任意字符。除了“[]”之外,還有兩個(gè)特殊的字符“^”和“-”:
字符 | 在字符類中的含義 |
---|---|
^ | 若出現(xiàn)在類的開頭,表示非此類中的字符 |
- | 若不出現(xiàn)在類的開頭和結(jié)尾,表示一個(gè)范圍,從“-”之前的字符到“-”之后的字符 |
常用的字符類及其簡稱:
R默認(rèn)正則簡稱 | Perl正則 | 字符類 | 含義 |
---|---|---|---|
[[:digit:]] | \\d | [0-9] | 數(shù)字 |
[^[:digit:]] | \\D | [^0-9] | 非數(shù)字 |
[[:alpha:]] | [a-zA-Z] | [a-zA-Z] | 英文字母 |
[[:alnum:]] | [a-zA-Z\\d] | [a-zA-Z0-9] | 英文字母或數(shù)字 |
[[:blank:]] | [ \\t] | [ \\t] | 空格、tab |
[[:space:]] | \\s | [ \\t\\r\\n\\f\\v] | 空白字符 |
[^[:space:]] | \\S | [^ \\t\\r\\n\\f\\v] | 非空白字符 |
\\w | [a-zA-Z0-9_] | 任意單詞字符 | |
\\W | [^a-zA-Z0-9_] | 任意非單詞字符 |
“()”用來分組,并可以使用后向引用“\\1”、“\\2”等來引用第一個(gè)括號(hào)中匹配到的內(nèi)容、第二個(gè)括號(hào)中匹配到的內(nèi)容等。例如,正則表達(dá)式“(a)(b)\\1”等價(jià)于正則表達(dá)式“aba”。
需要注意的是若在括號(hào)內(nèi)的開頭出現(xiàn)“?:”,則此分組不編號(hào),不可引用。例如,正則表達(dá)式“(?:a)(b)\\1”等價(jià)于正則表達(dá)式“abb”。
代碼 | 含義說明 |
---|---|
\w | 字符串,等價(jià)于[:alnum:] |
\W | 非字符串,等價(jià)于[^[:alnum:]] |
\s | 空格字符,等價(jià)于[:blank:] |
\S | 非空格字符,等價(jià)于[^[:blank:]] |
\d | 數(shù)字,等價(jià)[0-9] |
\D | 非數(shù)字,等價(jià)于[^0-9]
|
\b | Word edge(單詞開頭或結(jié)束的位置) |
\B | No Word edge(非單詞開頭或結(jié)束的位置) |
\< | Word beginning(單詞開頭的位置) |
\> | Word end(單詞結(jié)束的位置) |
R-base提供了grep,grepl,regexpr和regexec四個(gè)函數(shù)從字符串向量中提取符合正則表達(dá)式的匹配結(jié)果。它們的區(qū)別主要在返回的結(jié)果上。
這幾個(gè)函數(shù)的兩個(gè)主要參數(shù):
y1 = "Li lei likes Han meimei."y2 = "David likes Han meimei."y = c(y1, y2)y1y2ygrep(pattern="Li lei", x=y1)## [1] 1grep(pattern="Li lei", x=y2)## integer(0)grepl(pattern="Li lei", x=y1)## [1] TRUEgrepl(pattern="Li lei", x=y2)## [1] FALSEgrep(pattern="Li lei", x=y)## [1] 1grepl(pattern="Li lei", x=y)## [1] TRUE FALSEregexpr(pattern='l', text=y1)## [1] 4## attr(,"match.length")## [1] 1## attr(,"useBytes")## [1] TRUEgregexpr(pattern='l', text=y1)## [[1]]## [1] 4 8## attr(,"match.length")## [1] 1 1## attr(,"useBytes")## [1] TRUEregexec(pattern='l', text=y1)## [[1]]## [1] 4## attr(,"match.length")## [1] 1regexpr(pattern='l', text=y)## [1] 4 7## attr(,"match.length")## [1] 1 1## attr(,"useBytes")## [1] TRUEgregexpr(pattern='l', text=y)## [[1]]## [1] 4 8## attr(,"match.length")## [1] 1 1## attr(,"useBytes")## [1] TRUE## ## [[2]]## [1] 7## attr(,"match.length")## [1] 1## attr(,"useBytes")## [1] TRUEregexec(pattern='l', text=y)## [[1]]## [1] 4## attr(,"match.length")## [1] 1## ## [[2]]## [1] 7## attr(,"match.length")## [1] 1 |
默認(rèn)情況下,grep返回匹配到的字符串在字符串向量x中的下標(biāo)位置,沒有匹配到結(jié)果時(shí)返回integer(0);grepl返回與x等長的logical向量,表示是否與pattern匹配。
regexpr返回與text等長的integer向量,表示第一個(gè)匹配pattern的起始位置,-1表示不匹配,屬性match.length表示對(duì)應(yīng)匹配的匹配長度。
gregexpr返回與text等長的列表,列表的每個(gè)元素是相應(yīng)字符串的匹配結(jié)果。與regexpr不同的是,gregexpr會(huì)在字符串中找到所有匹配的子串并把結(jié)果返回,稱之為全局匹配。
regexec返回與text等長的列表,列表的每個(gè)元素是相應(yīng)字符串的匹配結(jié)果。與regexpr相同,regexec只返回第一個(gè)匹配到的結(jié)果。
參數(shù)ignore.cases用來控制正則匹配時(shí)是否是大小寫敏感的,默認(rèn)是大小寫敏感的。
grep(pattern='li lei', x=y, ignore.case = TRUE)## [1] 1grepl(pattern='li lei', x=y, ignore.case = TRUE)## [1] TRUE FALSE |
grep函數(shù)可以通過設(shè)置參數(shù)value=TRUE,直接返回匹配到的整個(gè)字符串。
grep(pattern='Li lei', x=y, value=TRUE)## [1] "Li lei likes Han meimei." |
grep函數(shù)的參數(shù)invert用來控制返回的結(jié)果是匹配正則表達(dá)式的或不匹配正則表達(dá)式的結(jié)果。默認(rèn)invert=FALSE,返回的是匹配正則表達(dá)式的結(jié)果;當(dāng)invert=TRUE時(shí),返回的是不匹配正則表達(dá)式的結(jié)果。
grep(pattern='Li lei', x=y, invert = TRUE)## [1] 2 |
在R中,提取符合pattern的子字符串需要經(jīng)過兩個(gè)步驟:先使用regexpr / gregexpr/regexec匹配,再使用regmatches提取。
regmatches的兩個(gè)主要參數(shù):
s = 'aababaaaba'# 返回第一個(gè)匹配的部分m = regexpr(pattern='a+', text=s)regmatches(x=s, m)## [1] "aa"# 返回每一個(gè)匹配的部分mg = gregexpr(pattern='a+', text=s)regmatches(x=s, mg)## [[1]]## [1] "aa" "a" "aaa" "a"# 返回第一個(gè)匹配的部分m = regexec(pattern='a+', text=s)regmatches(x=s, m)## [[1]]## [1] "aa" |
R-base提供了兩個(gè)替換子字符串的函數(shù)sub和gsub。把目標(biāo)字符串中與pattern匹配的部分用replacement代替。這兩個(gè)函數(shù)的不同之處在于gsub會(huì)把目標(biāo)字符串中所有與pattern匹配的部分用replacement代替。
sub(pattern='Li lei', replacement='Xiao ming', x=y)## [1] "Xiao ming likes Han meimei." "David likes Han meimei."gsub(pattern='l', replacement='L', x=y)## [1] "Li Lei Likes Han meimei." "David Likes Han meimei." |
strsplit函數(shù)可以使用正則表達(dá)式作為拆分標(biāo)志來把字符串拆分成向量。需要注意的是strsplit返回的是列表。
# 目標(biāo)字符串中的數(shù)字使用不定個(gè)數(shù)的空白分隔開strsplit('1 2 3 4', '\\s+')## [[1]]## [1] "1" "2" "3" "4"# unlist可以去掉list結(jié)構(gòu)unlist(strsplit('1 2 3 4', '\\s+'))## [1] "1" "2" "3" "4" |
strings <- c( "apple", "219 733 8965", "329-293-8753", "Work: 579-499-7527; Home: 543.355.3679")phone <- "([0-9]{2})[- .]([0-9]{3})[- .]([0-9]{4})"
在上面的代碼中,變量strings里保存的是一個(gè)字符串向量,我們的目標(biāo)是想檢驗(yàn)該向量中的每一個(gè)字符串是否包含有電話號(hào)碼。顯然,直接精確搜索是不可能的,因?yàn)槊總€(gè)電話號(hào)碼的內(nèi)容不盡相同。但是由于電話號(hào)碼都有相似的格式,這時(shí)候正則表達(dá)式就派上了用處。
我們定義了一個(gè)正則表達(dá)式phone,就是為了抓取這樣的格式:[0-9]表示我門要匹配一個(gè)數(shù)字,用花括號(hào){2}則表示[0-9]重復(fù)兩次,即開頭有兩個(gè)數(shù)字,[- .]表示數(shù)字之間的分隔符可以“-”也可以是“ ”和“.”。
最終我們用grep函數(shù)就可以得到如下結(jié)果:
> grep(phone,strings)[1] 2 3 4
這個(gè)函數(shù)的意思是用在strings里用phone所保存的正則表達(dá)式進(jìn)行匹配。顯然第一個(gè)字符串中沒有包含電話號(hào)碼,而其后三個(gè)字符串都是有電話的,這和strings變量保存的實(shí)際情況相符。
通過這個(gè)例子,相信大家對(duì)正則表達(dá)式已經(jīng)有了初步的理解,下面就歸納一些正則表達(dá)式的常用寫法:
1.匹配字符串:
首先,一個(gè)括號(hào)代表匹配一個(gè)位置, 想匹配某個(gè)字母就可以用把該字符寫到括號(hào)里,多個(gè)括號(hào)連在一起就可以匹配一個(gè)字符串:
比如[a]表示匹配字符'a',同理[\]表示匹配字符‘\’[c][a][t]表示匹配"cat"字符串當(dāng)然實(shí)際上如果只是匹配一個(gè)字符,完全可以不用中括號(hào),直接寫出cat就好
中括號(hào)在下列情況下才是必須要加的:即有時(shí)候在某一位置可能會(huì)出現(xiàn)幾種可能的字符。這時(shí)我們便將這些字符都寫入一個(gè)括號(hào)中,表達(dá)一種“或”的關(guān)系。
比如[aoeiu]就表示匹配'a'或'o'或'i'或'o'或'u',也就是表示匹配一個(gè)元音字母同理[0-9]表示匹配0-9之間的任何一個(gè)數(shù),也就是表示匹配一個(gè)數(shù)字[0123456789]也可以簡寫為[0-9],于是匹配一個(gè)年份就可以寫成[0-9][0-9][0-9][0-9]
[0-9]這種寫法顯然就比[0123456789]更為簡明,這在正則表達(dá)式里非常常見,字母也可以這樣寫:
[d-f]表示匹配'd'或'e'或'f'值得注意的是,正則表達(dá)式是區(qū)分大小寫的,于是[D-F]表示匹配'D'或'E'或'F'如果想匹配任意一個(gè)大寫字母就可以寫成[A-Z]如果相匹配任意一個(gè)字母就可以寫成[a-zA-Z]
此外我們還需知道'.','^',等特殊字符的用法:
'.'表示匹配任何一個(gè)字符,即通配符于是[....]就表示匹配任何一個(gè)長度為4的字符串,或者直接用....'^'表示“非”,就是表示對(duì)后面內(nèi)容的否定,即不包含哪個(gè)字符比如[^0-9]就表示除了數(shù)字之外的任何字符
還有幾種特殊的字母,也是為了方便大家使用:
比如\\d等價(jià)為[0-9],即任何一個(gè)數(shù)字,[0-9][0-9][0-9][0-9]也可寫為dddd\w等價(jià)為[^0-9a-zA-Z],即非數(shù)字非字母的任何一個(gè)字符\s等價(jià)為[.]即任何一個(gè)字符
現(xiàn)在我們做一個(gè)練習(xí),如果我們想匹配2016-10-18這種形式的日期格式,我們應(yīng)該用什么樣的正則表達(dá)式呢?
如果仔細(xì)閱讀了以上的內(nèi)容,相信這個(gè)不是很難,應(yīng)為如下:
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]或者dddd-dd-dd
然而這樣寫是不是還是太繁瑣了呢?如果我要匹配36個(gè)連續(xù)的數(shù)字,難道也把[0-9]或者'd'復(fù)制36遍嗎?
其是我們完全可以用花括號(hào)內(nèi)的數(shù)字告訴正則表達(dá)式我們想要重復(fù)的次數(shù),用下列方式實(shí)現(xiàn):
[0-9]{4}-[0-9]{2}-[0-9]{2}或d{4}-d{2}-d{2}實(shí)現(xiàn)
這樣是不是就簡單多了?
花括號(hào)里也不僅僅只能是一個(gè)數(shù)字,還有其他用法:
比如a{2,6}就表示匹配"aa"或"aaaaaa"a{2-4}就表示匹配"aa","aaa","aaaa"colou{0,1}r就表示匹配"color","colour"
更為精妙的,花括號(hào)內(nèi)的數(shù)字可以是開區(qū)間:
a{2,}表示匹配連續(xù)次數(shù)兩次以上的'a',即"aa","aaa","aaaa"....{0,}表示匹配任何文本,因?yàn)檫@是一個(gè)通配符'.'重復(fù)任何次數(shù),由于我們是從0到正無窮,所以即便是一個(gè)空文本也可以被匹配到我們利用上面的寫法就可以輕松匹配到一對(duì)雙引號(hào)及里面的文本:".{0,}"但是上述寫法可能匹配到 "sdf"sdf"sdf"這樣的文本,即雙引號(hào)里還有雙引號(hào)。這時(shí)候我們可以用"[^"]{0,}"保證找到的雙引號(hào)里面沒有包括其他任何雙引號(hào);此外我們還可以用".*?"實(shí)現(xiàn)上述效果。本來".*?"中'.*'表示任何文本,但在其之后加上'?'之后就表示匹配最少的字符,故而就不會(huì)出現(xiàn)匹配的雙引號(hào)里文本還具有雙引號(hào)的問題了。
正則表達(dá)式提供了一些字符刻畫常見的花括號(hào)區(qū)間方法,比如:
'*'表示{0,},于是任何文本也可以通過".*"匹配'?'表示{0,1},比如colou?r也可以匹配"color","colour"'+'表示{1,},于是匹配一個(gè)及以上的 數(shù)字可以用d+
其他:
cat|dog可以表示匹配"cat"或"dog"Mon|Tues|Wednes|Thurs|Fri|Satur|Sun|day表示匹配一周內(nèi)任何一天
2.匹配段落:
^表示匹配行的開始位置$表示匹配行的結(jié)束位置^&表示一個(gè)空行^.*& 表示匹配全文內(nèi)容,因?yàn)樾械拈_始符號(hào)也是一個(gè)字符,"."會(huì)匹配這個(gè)符號(hào)。找到單獨(dú)的一行,可以使用 ^.*?$
3.正則表達(dá)式函數(shù)
3.1 grep()
我們之前使用了一個(gè)正則表達(dá)式函數(shù)grep(),下面就細(xì)細(xì)講講正則表達(dá)式函數(shù):
text = c("to be", "or not to", "be it is", "a question")pat = "be"grep(pat, text)
這個(gè)函數(shù)很簡單,結(jié)果如下:
> grep(pat, text)[1] 1 3
返回的是一個(gè)向量,告訴我們原字符串的哪些下標(biāo)的字符串匹配成功了。
我們也可以加一個(gè)value屬性,返回的就是被匹配到的字符串組成的向量了。
> grep(pat, text, value=TRUE)[1] "to be" "be it is"
此外,用invert屬性可以返回沒有被匹配到的下標(biāo)向量:
> grep(pat, text, invert = TRUE)[1] 2 4
3.2 grepl()
> grepl(pat, text)[1] TRUE FALSE TRUE FALSE
這個(gè)函數(shù)返回的是邏輯向量
3.3 regexpr()
> regexpr(pat, text)[1] 4 -1 1 -1attr(,"match.length")[1] 2 -1 2 -1attr(,"useBytes")[1] TRUE
這個(gè)函數(shù)返回前兩行表示匹配部分在字符串中的起始位置與長度,-1表示未匹配到。因?yàn)槲覀兤ヅ涞氖?be"在"to be"中就是第四個(gè)位置開始匹配,然后持續(xù)兩個(gè)長度,也就是返回值里的前兩行第一個(gè)下標(biāo)位置的返回值4和2。
3.4 gregexpr(pat, text)
> gregexpr(pat, text)[[1]][1] 4attr(,"match.length")[1] 2attr(,"useBytes")[1] TRUE[[2]][1] -1attr(,"match.length")[1] -1attr(,"useBytes")[1] TRUE[[3]][1] 1attr(,"match.length")[1] 2attr(,"useBytes")[1] TRUE[[4]][1] -1attr(,"match.length")[1] -1attr(,"useBytes")[1] TRUE
這個(gè)函數(shù)和上一個(gè)regexpr()很相似,只不過返回的是一個(gè)list。
模式替換與拆分函數(shù)
在這里我不會(huì)講R語言的自帶模式替換與拆分,因?yàn)樵谙缕恼吕镂視?huì)介紹stringr這個(gè)字符串處理的神包。
聯(lián)系客服