摘 要:正則表達式是一個重要的編程概念。應用正則表達式能夠實現不少強大的字符處理功能,有時也能夠爲常規方法解決起來比較複雜的問題另闢蹊徑。本文試圖經過比較詳盡的示例爲沒有基礎的讀者介紹正則表達式的基本概念、用法及其在Matlab中的實現。文末附上幾個應用表達式解決實際問題的實例,以利於讀者在本身的實踐中應用。html
關 鍵 詞:正則表達式 Matlab regexp前端
1. 引言正則表達式
正則表達式就是一個表達式(也是一串字符),它定義了某種字符串模式——利用正則表達式,能夠算法
對大段的文字進行復雜的查找、替換等。本文將以 Matlab 爲編程語言,講解正則表達式的概念和使用方法,編程
並將在文末以實例說明正則表達式的實踐應用。數組
Matlab 提供的正則表達式函數有三個:安全
regexp——用於對字符串進行查找,大小寫敏感;session
regexpi——用於對字符串進行查找,大小寫不敏感;app
regexprep——用於對字符串進行查找並替換。electron
簡要介紹一下這三個函數,以 regexpi 爲例 —— 讀者能夠先跳過這裏,看過全文以後再來看這裏。
用法 1:
[start end extents match tokens names] = regexpi('str', 'expr')
start 爲匹配字符串的起始位置;end 爲匹配字符串的終止位置;extents 爲擴展內容,和'tokens'指示符
一塊兒用,指示出現 tokens 的位置;match 即找到的匹配字串;tokens 匹配正則表達式中標記(tokens)的字串;
names 爲匹配到的命名標記的標記名。
用法 2:
若不須要全部的輸出,能夠用下面的方式有選擇的輸出。
[v1 v2 ...] = regexpi('str', 'expr', 'q1', 'q2', ...)
'q1'、'q2' ...... 爲 'start'、'end'、'tokens'、'tokensExtents'、'match'、'names' 之一,意義與前文相同。v一、
v2...... 的輸出順序與 q一、q2...... 一致。
2. 單個字符的匹配
咱們先從簡單的開始 —— 以 regexpi 函數爲例,不區分字符的大小寫。假設你要搜索 'cat',搜索用
的正則表達式就是 'cat',這與文本編輯工具裏經常使用的 CTRL+F 是同樣的,即正則表達式 'cat' 匹配 'cat'、
'Cat'、'cAt'、'CAt'、'caT'、'CaT'、'cAT'、'CAT'。
爲了方便,下面的敘述中字符串和正則表達式的''都省略不寫。
2.1 句點符號
. —— 匹配任意一個(只有一個)字符(包括空格)。
假設你在玩英文拼字遊戲,想要找出三個字母的單詞,並且這些單詞必須以 't' 字母開頭,以 'n' 字母結束;另外,有一本英文字典,你能夠用正則表達式搜索它的所有內容。要構造出這個正則表達式,你可使用一個通配符 —— 句點符號 '.' 。這樣,完整的表達式就是 t.n,它匹配 tan、ten、tin 和 ton,還匹配 t#n、tpn 甚至 t n,還有其餘許多無心義的組合。這是由於句點符號匹配全部字符,包括空格,即:正則表達式 t.n 匹配 ten、tin、ton、t n、tpn、t#n、t@n 等。
Matlab 程序實例:
clear;clc
str='ten,&8yn2tin6ui>&ton, t n,-356tpn,
t#n,4@).,t@nT&nY';
pat='t.n';
o1=regexpi(str,pat,'start') %用'start'指定輸出 o1 爲匹配正則表達式的子串的起始位置
o2=regexpi(str,pat,'end') %用'end'指定輸出 o2 爲匹配正則表達式的子串的結束位置
o3=regexpi(str,pat,'match') %用'match'指定輸出 o3 爲匹配正則表達式的子串
[o11,o22,o33]=regexpi(str,pat,'start','end','match') %同時輸出起始位置和字串
輸出爲:
o1 = 1 10 18 23 31 39 48 51
o2 = 3 12 20 25 33 41 50 53
o3 = 'ten' 'tin' 'ton' 't n' 'tpn' 't#n' 't@n' 'T&n'
o11 = 1 10 18 23 31 39 48 51
o22 = 3 12 20 25 33 41 50 53
o33 = 'ten' 'tin' 'ton' 't n' 'tpn' 't#n' 't@n' 'T&n'
2.2 方括號符號
[oum] —— 匹配方括號中的任意一個。
爲了解決句點符號匹配範圍過於普遍這一問題,你能夠在方括號([])裏面指定看來有意義的字符。此時,只有方括號裏面指定的字符才參與匹配。也就是說,正則表達式 t[aeio]n 只匹配 tan、Ten、tin 和 toN等。但 Tmn、taen 不匹配,由於在方括號以內你只能匹配單個字符。
Matlab 程序實例:
clear;clc
str='ten,&8yn2tin6ui>&ton, t n,-356tpn,
t#n,4@).,t@nT&nY';
pat='t[aeiou]n';
[o11,o22,o33]=regexpi(str,pat,'start','end','match')
o11 = 1 10 18
o22 = 3 12 20
o33 = 'ten' 'tin' 'ton'
2.3 方括號中的鏈接符
'[c1-c2]' —— 匹配從字符 c1 開始到字符 c2 結束的字母序列(按字母表中的順序)中的任意一個。例如 [a-c] 匹配 a、b、c、A、B、C,即正則表達式 t[a-z]n 匹配 tan、tbn、tcn、tdn、ten、……、txn、tyn、tzn。
Matlab 程序實例:
clear;clc
str='ten,&8yn2tin6ui>&ton, t n,-356tpn,
t#n,4@).,t@nT&nY';
pat='t[a-z]n';
[o11,o22,o33]=regexpi(str,pat,'start','end','match')
o11 = 1 10 18 31
o22 = 3 12 20 33
o33 = 'ten' 'tin' 'ton' 'tpn'
2.4 特殊字符
\.等 —— 即由 '\' 引導的,表明有特殊意義或不能直接輸入的單個字符。
在使用 fprintf 函數輸出時咱們經常使用 '\n' 來代替回車符,這裏也是一樣的道理,用 \n 在正則表達式中表示回車符。相似的還有 \t 橫向製表符,'\*' 表示 '*' 等。後一種狀況用在查詢在正則表達式中有語法做用的字符,詳見下文。
下面是一些匹配單個字符的轉義字符正則表達式及所匹配的值。
\xN 或\x{N} 匹配八進制數值爲 N 的字符
\oN 或\o{N} 匹配十六進制數值爲 N 的字符
\a Alarm(beep)
\b Backspace
\t 水平 Tab
\n New line
\v 垂直 Tab
\f 換頁符
\r 回車符
\e Escape
\c 某些在正則表達式中有語法功能或特殊意義的字符 c,要用 \c 來匹配,而不能直接用 c 匹配,例如 . 用正則表達式 \. 匹配,而 \ 用正則表達式 \\ 匹配。
Matlab 程序實例:
clear;clc
str='l.[a-c]i.$.a';
pat1='.';pat2='\.';
o=regexpi(str,pat1,'match')
o1=regexpi(str,pat2,'match')
輸出爲:
o = 'l' '.' '[' 'a' '-' 'c' ']' 'i' '.' '$' '.' 'a'
o1 = '.' '.' '.'
2.5 類表達式
\w、\s 和 \d 等 —— 匹配某一類字符中的一個。
和上面的 \n 等表中的轉義字符有所不一樣,\w、\s、\d 等匹配的不是某個特定的字符,而是某一類字符。具體說明以下:
\w 匹配任意的單個文字字符,至關於 [a-zA-Z0-9_];
\s 匹配任意的單個空白字符,至關於 [\t\f\n\r];
\d 匹配任意單個數字,至關於 [0-9];
\S 匹配除空白符之外的任意單個字符,至關於 [^\t\f\n\r] —— 方括號中的^表示取反;
\W 匹配任意單個字符,至關於 [^a-zA-Z0-9_];
\D 匹配除數字字符外的任意單個字符,至關於 [^0-9]。
Matlab 程序實例:
s='This city has a population of more than 1,000,000.';
ptn='\d';
regexp(s,ptn,'match')
輸出爲:
ans = '1' '0' '0' '0' '0' '0' '0'
3.字符串的匹配
3.1 屢次匹配
例如須要匹配 'ppp',那麼就能夠用正則表達式 'ppp',還有一種更簡單一點的記法 'p{3}'。正則表達式中的 '{}' 用來表示匹配前面的表達式的出現次數,即 'p{2,3}' 匹配 'pp' 和 'ppp'。除了 '{}',還有幾個
字符,用在表示單個字符的正則表達式後面表示次數,以下所述:
expr? 與 expr 匹配的元素出現 0 或 1 次,至關於{0,1}
expr* 與 expr 匹配的元素出現 0 次或更多,至關於{0,}
expr+ 與 expr 匹配的元素出現 1 次或更多,至關於{1,}
expr{n} 與 expr 匹配的元素出現 n 次,至關於{n,n}
expr{n,} 與 expr 匹配的元素至少出現 n 次
expr{n,m} 與 expr 匹配的元素出現 n 次但很少於 m 次
假設咱們要在文本文件中搜索美國的社會安全號碼。這個號碼的格式是 999-99-9999。用來匹配它的正則表達式爲 [0-9]{3}\-[0-9]{2}\-[0-9]{4}。在正則表達式中,連字符(「-」)有着特殊的意義,所以,它的前面要加上一個轉義字符 \。
若是但願連字符號能夠出現,也能夠不出現 —— 即 999-99-9999 和 999999999 都屬於正確的格式。這時,你能夠在連字符號後面加上 '?' 數量限定符。這樣正則表達式爲 [0-9]{3}\-?[0-9]{2}\-?[0-9]{4}。另外,當咱們使用 expr* 時,Matlab 將盡量的匹配最長的字符子串。如:
>>str = '<tr valign=top><td><a name="19184"></a>xyz';
>>regexp(hstr, '<.*>', 'match')
ans = '<tr valign=top><td><a name="19184"></a>'
若是咱們但願匹配儘量短的字符子串時,能夠在上面咱們使用的字符串後使用 '?',也就是 expr*?,
例如:
>>str = '<tr valign=top><td><a name="19184"></a>xyz';
>>regexp(hstr, '<.*?>', 'match')
ans = '<tr valign=top>' '<td>' '<a name="19184">' '</a>'
這個表達式的執行過程是這樣的,先執行 expr*,「遊標」(若是有的話)就指到了與 expr* 匹配的字符子串的最末端,而後從那裏開始再檢查下一個字符與後面的表達式是否匹配,若是匹配就繼續向前(若是一直成功則返回最長的字符串),若是不匹配則直接返回空。例如:
>>str = '<tr valign=top><td><a name="19184"></a>xyz';
>>regexp(hstr, '<.*+>', 'match')
ans = {}
>>regexp(hstr, '<.*+', 'match')
ans = '<tr valign=top><td><a name="19184"></a>xyz'
3.2 邏輯運算符
exp|exp2 表示或者知足 exp 或者知足 exp2。
(expr) 將 expr 標記爲一組,匹配 expr,並將匹配的字符子串標記起來以供後面使用。關於這部份內容下面還會有更詳細介紹。
(?:expr) 表示 expr 爲一組,至關於數學表達式中的()
例如:
lstr='A body or collection of such stories';
regexp(lstr,'(?:[^aeiou][aeiou]){2,}','match')
ans = 'tori'
上面的表達式中 {2,} 對 [^aeiou][aeiou] 起做用,若是去掉分組,則只對 [aeiou] 起做用,以下所示:
>>regexp(lstr,'[^aeiou][aeiou]{2,}','match')
ans = 'tio' 'rie'
(?>expr) expr 中的每一個元素是一個分組。(?#expr) 放在(?#和)之間的是註釋。如:
>>regexp(lstr, '(?# Match words in caps)[A-Z]\w*', 'match')
ans = 'A'
expr1|expr2 匹配 expr1 或者 expr2 二者之一便可。
>>regexp(lstr, '[^aeiou\s]o|[^aeiou\s]i', 'match')
ans = 'bo' 'co' 'ti' 'to' 'ri'
^expr 匹配 expr,而且出如今原字符串最前端的子串。expr$ 匹配 expr,而且出如今原字符串最末端的子串。
>>pi(lstr, '^a\w*|\w*s$', 'match')
ans = 'A' 'stories'
\<expr 匹配 expr,而且出如今一個單詞最前端的子串。
>> regexpi(lstr, '\<s\w*', 'match')
ans = 'such' 'stories'
expr\> 匹配 expr,而且出如今一個單詞最末端的子串。
>> regexpi(lstr, '\w*tion\>', 'match')
ans = 'collection'
\<expr\> 更嚴格的單詞匹配,如:以 s 開頭,而且以 h 結尾的單詞。
>>regexpi(lstr, '\<s\w*h\>', 'match')
ans = 'such'
3.3 左顧右盼 —— 利用上下文匹配
利用上下文的匹配來找到咱們要找的內容。expr1(?=expr2) 找到匹配 expr1 的子串,若是其後的字符串也匹配 expr2。以下面的例子查找全部在','以前的單詞。
s='Grammar Of, relating to, or being a noun or pronoun case that indicates possession.';
ptn='\w*(?=,)';
regexp(s,ptn,'match')
ans = 'Of' 'to'
expr1(?!expr2) 找到匹配 expr1 的子串若是其後的字符串不匹配 expr2。下面的例子匹配全部不在','以前的單詞:
>>regexpi(s, '\w*(?!=,)', 'match')
ans= 'Grammar' 'Of' 'relating' 'to' 'or' 'being' 'a' noun' 'or' 'pronoun' 'case' 'that' 'indicates' 'possession'
(?<=expr1)expr2 找到匹配 expr2 的子串,若是其前面的字符串也匹配 expr1。下面的例子查找全部在 ','以後的單詞,注意 ',' 以後可能有空格。
>>regexpi(s,'(?<=,\s*)\w*','match')
ans = 'relating' 'or'
(?<!expr1)expr2 找到匹配 expr2 的子串,若是其後的字符串不匹配 expr1。下面的例子查找全部不在','以後的單詞:
>>regexpi(s,'(?<!,\s*)\w*','match')
ans= 'Grammar' 'Of' 'elating' 'to' 'r' 'being' 'a' 'noun' 'or' 'pronoun' 'case' 'that' 'indicates' 'possession'
4.標記(tokens)
這部分是比較難的一部分,可是應用得當能夠實現很是強大的功能。
4.1 什麼是標記(tokens)
任何的正則表達式均可以用圓括號括起來做爲一個標記。例如,建立一個記錄錢數的標記,就能夠用($\d+)。這樣與之匹配的字符串就會被記錄下來,根據這個標記出現的順序,可使用 \n 來引用匹配這個標記的字符串。如 \3 來引用與標記相匹配的第三個字符串。(若是在替換函數 regexprep 中,須要用 $3
來引用。) 下面是一個例子,\S 查找任意的非空白字符,\1 用來講明要匹配第一個 tokens 的內容,也就是要當即再次查找剛剛匹配到的同一個字符,而且要緊挨着第一個。'tokens' 選項用來向 tok 輸出全部匹配到的標記;而 'tokenExtents' 則用來表示匹配標記的起始位置。
s='Grammar Of, relating to, or being a noun or pronoun case that indicates possession.';
[mat,tok,ext]=regexpi(s, '(\S)\1','match','tokens','tokenExtents')
>>mat
mat = 'mm' 'ss' 'ss'
>>tok{:}
ans = 'm'
ans = 's'
ans = 's'
>>ext{:}
ans = 4 4
ans = 75 75
ans = 78 78
4.2 如何使用標記?
(expr) 記錄全部匹配表達式的字符,並作爲一個標記,以備後面使用。如上面的例子,利用標記實現查找連續的重複字母。
\N 匹配同一條正則表達式裏的第 N 個標記中的字符串,例如 \1 匹配第一個標記。下面的例子能夠查找 html 語句中相似<a>abc</a>的部分:
hstr = '<!comment><tr nam="7507"></tr><table>Default</table><br>';
expr = '<(\w+).*?>.*?</\1>';
[mat tok] = regexp(hstr, expr, 'match', 'tokens');
>> mat{:}
ans = <tr nam="7507"></tr>
ans = <table>Default</table>
>> tok{:}
ans = 'tr'
ans = 'table'
$N 在一個替換字符串中插入與第 N 個標記相匹配的字符串(只用於 regexprep 函數)。下面的例子能夠將匹配到的第一個 token 和第二個 token 的位置互換:
>> regexprep('Norma Jean Baker', '(\w+\s\w+)\s(\w+)', '$2, $1')
ans = Baker, Norma Jean
(?<name>expr) 記錄全部匹配表達式 expr 的字符,作爲一個標記,並設定一個名字 name。\k<name>
與名爲 name 的標記相匹配。下面這個例子和這部分第一個例子是同樣的,只不過使用了命名的標記。
>>poestr = ['While I nodded, nearly napping, ' ...
'suddenly there came a tapping,'];
>>regexp(poestr, '(?<nonwhitechar>\S)\k<nonwhitechar>', 'match')
ans = 'dd' 'pp' 'dd' 'pp'
(?(tok)expr) 若是標記 tok 已經產生,則匹配表達式 expr。if-then 結構。其中的標記能夠是數字標記,也能夠是命名標記。 (?(tok)expr1|expr2) 若是標記 tok 已經產生,則匹配表達式 expr1,不然匹配表達式 expr2。if-then-else結構下面的例子用來檢查一個句子中的性別用詞是否匹配,表達式的意思就是,若是前面用的是 'Mrs' 那
麼後面就匹配 'her',若是前面用的是'Mr',也就是沒有匹配到 'Mr' 後面的 's',則後面匹配 'his'。
>>expr = 'Mr(s?)\..*?(?(1)her|his) son';
>>[mat tok] = regexp('Mr. Clark went to see his son', expr, 'match', 'tokens')
mat = 'Mr. Clark went to see his son'
tok = {1x2 cell}
>>tok{:}
ans = '' 'his'
若是把句子中的 his 改爲 her,則沒有與之匹配的結果。
>>[mat tok] = regexp('Mr. Clark went to see her son', expr, 'match', 'tokens')
mat = {}
tok = {}
5.多行字符串與多正則表達式
5.1 多字符串與單個正則表達式匹配
多個字符串存在一個元胞數組裏以後,每個字符串與正則表達式匹配,返回值的維數與元胞數組相同。
cstr = { ...
'Whose woods these are I think I know.' ; ...
'His house is in the village though;' ; ...
'He will not see me stopping here' ; ...
'To watch his woods fill up with snow.'};
>>idx{:}
ans = % 'Whose woods these are I think I know.'
8 % |8
ans = % 'His house is in the village though;'
23 % |23
ans = % 'He will not see me stopping here'
6 14 23 % |6 |14 |23
ans = % 'To watch his woods fill up with snow.'
15 22 % |15 |22
5.2 多個字符串與多個正則表達式匹配
這種狀況下,應該知足字符串元胞數組中字符串的個數和正則表達式的個數相等——但維數不必定要相等——如能夠用 4*1 的元胞數組與 1*4 的正則表達式相匹配。
expr = {'i\s', 'hou', '(.)\1', '\<w[aeiou]'};
idx = regexpi(cstr, expr);
idx{:}
ans = % 'Whose woods these are I think I know.'
23 31 % |23 |31
ans = % 'His house is in the village though;'
5 30 % |5 |30
ans = % 'He will not see me stopping here'
6 14 23 % |6 |14 |23
ans = % 'To watch his woods fill up with snow.'
4 14 28 % |4 |14 |28
5.3 多字符串的替換
這個功能是在匹配的基礎上,在正則表達式後面加入要替換的字符串便可。下面這個是 matlab 中的例子,很容易理解。
>>s = regexprep(cstr, '(.)\1', '--', 'ignorecase')
s = 'Whose w--ds these are I think I know.'
'His house is in the vi--age though;'
'He wi-- not s-- me sto--ing here'
'To watch his w--ds fi-- up with snow.
6.應用實例
問題 1:查找包含某個字串的串。例如:
在 str= {'apple_food', 'chocolates_food','ipod_electronics','dvd_player_electronics', 'water_melon_food'}
中查找字串'food',獲得結果 [1 1 0 0 1]。
str = {'apple_food','chocolates_food','ipod_electronics','dvd_player_electronics', 'water_melon_food'};
ptn='food';
m1=regexp(str,ptn,'match');
ix=~cellfun('isempty',m1);
問題 2:如何將 Matlab 中的 ^ 轉換成 C 語言?如將 a^b 轉換成 a**b,或者 pow(a,b)。如下式爲例:
s=1/2*w/(1+Pf^2*Pc-Pf^2*Pc*w1-w1*Pf^2-Pf*Pc-Pf^2*w^2+2*w1*Pf-2*Pf)
Matlab 提供了 ccode 命令,用於將 Matlab 轉換爲 c,這裏僅爲一例:
s='1/2*w/(1+Pf^2*Pc-Pf^2*Pc*w1-w1*Pf^2-Pf*Pc-Pf^2*w^2+2*w1*Pf-2*Pf)';
ptn='(\w{1,2})\^(\d{1})';
regexp(s,ptn,'tokens');
s1=regexprep(s,ptn,['pow(','$1',',','$2',')'])
問題 3:刪掉<和/>和它們之間的部分,例如:
處理前:Hello <a href="world">world</a>. 2 < 5
處理後:Hello world. 2 < 5
ss='Hello <a href="world">world</a>. 2 < 5';
b='<.*?>';
sr=regexprep(ss,b,'')
問題 4:遊程平滑算法:將連續的且個數小於某個閾值的 0 所有替換成 1,例如:
平滑前:1111100000111100011
平滑後:1111100000111111111
a = [1 0 0 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1];
T = 4;
b = sprintf('%d',a);
b1 = regexprep(b,'(?<!0)0{1,3}(?!0)', repmat('1', size('$0')));
a1=b1-48