一、捕獲組
God said, "Let there be light", and there was light; and God saw that the light was good, and he separated light from darkness. He called the light day, and the darkness night. So evening came, and morning came, the first day.
我們在閱讀文章時,常常會根據其中一些詞語出現的頻度來推斷出其中心含義,當然了,更加深層的含義還需要慢慢體會,不過我們可以利用這個簡單的方法快速地抓取某些關鍵詞。上面的一段文字摘自圣經創世紀,我們如何找出一個出現次數最多的詞呢?這個問題就可以利用本章標題提到的捕獲組和逆向引用來解決!
我們先給出答案 (/b/w{3,}/b)(.*?/b/1/b){4,} ,在本章的最后將對這個答案進行詳細解釋。
在正則表達式中,可以使用圓括號來把字符組織到一起,并把匹配的結果記錄下來,我們把組織到一起的字符稱為一個捕獲群。捕獲群的行為與單個字符類似,允許與操作符一起配合使用,如“*”、“|”、“?”等。其中,圓括號也屬于元字符的范疇之內,如要與圓括號進行匹配,需要事先進行轉義操作。
現在讓我們舉個例子來看看到底應該如何使用捕獲組。下面的表格是一些條碼基本術語,每條術語都是由名稱和解釋兩部分組成,現在的任務就是讓術語名稱和術語解釋兩個部分進行調換。這時,捕獲組就派上用場了,因為它不光能進行匹配操作,還能把匹配的結果記錄下來并進行編號,以便我們以后利用。
-
<html>
-
<head>
-
<title>
Terms
</title>
-
</head>
-
-
<body>
-
<table>
-
<tr>
-
<td>
條碼 bar code
</td>
-
<td>
由一組規則排列的條、空及其對應字符組成的標記,用以表示一定的信息。
</td>
-
</tr>
-
<tr>
-
<td>
條碼系統 bar code system
</td>
-
<td>
由條碼符號設計、制作及掃描閱讀組成的自動識別系統。
</td>
-
</tr>
-
<tr>
-
<td>
條 bar
</td>
-
<td>
條碼中反射率較低的部分。
</td>
-
</tr>
-
<tr>
-
<td>
空 space
</td>
-
<td>
條碼中反射率較高的部分。
</td>
-
</tr>
-
</table>
-
</body>
-
</html>
首先,我們先建立匹配用的正則表達式 (<td>.+?</td>)(<td>.+?</td>) 。從表面上看,捕獲組的兩個部分完全一樣,那我們為什么要使用兩 (<td>.+?</td>) ,而不是一個 (<td>.+?</td>) 呢?這是因為我們要分別匹配術語名稱和術語解釋,然后把匹配后記錄下來的結果進行對調,這樣就能把術語名稱和術語解釋兩部分的位置交換。下面是匹配后的結果:
<table>
<tr>
<td>條碼 bar code </td><td>由一組規則排列的條、空及其對應字符組成的標記,用以表示一定的信息。</td> </tr>
<tr>
<td>條碼系統 bar code system </td> <td>由條碼符號設計、制作及掃描閱讀組成的自動識別系統。</td> </tr>
<tr>
<td>條 bar </td><td>條碼中反射率較低的部分。</td> </tr>
<tr>
<td>空 space </td><td>條碼中反射率較高的部分。</td> </tr>
</table>
二、捕獲群的編號
接下來,我們學習一下與捕獲群密切相關的捕獲群編號的概念。當使用含有捕獲群的正則表達式進行匹配操作時,引擎會為每一對小括號組合進行編號,如正則表達式 (正[規則])(表[達現](式)) ,引擎就對它進行了四次編號。首先是 (正[規則]) ,然后是 (表[達現](式)) ,最后是 (式) 。咦?!不是說四次編號嗎,怎么少了一次。其實還有一個默認的最大的捕獲群,它就是完整的正則表達式本身 (正[規則])(表[達現](式)) 。
每個捕獲群的編號代表了它所指向的匹配內容,通過它,就能夠重復利用匹配的結果。捕獲群的編號格式是一個元字符“/”或“$”(一般以“$”更為常見)加上一個數字,數字的范圍在 0 至 9 之間。 /0 或 $0 代表整個正則表達式相匹配的結果,其余的與它們各自指向的捕獲群所匹配的結果相對應的。
三、配合使用捕獲群與其編號
剛才我們已經把成功地找到了術語名稱和術語解釋兩個部分,現在來看看如何實現位置調換。在每一次匹配操作中,兩個 (<td>.+?</td>) 分別被編上兩個序號,我們使用 $1 和 $2 就能引用它們,所以,調換位置實際上就是對這兩個引用進行調換。下面我們用Javascript代碼演示一下。

首先先將要調換的表格源代碼放到一個多行文本區域里,然后通過點擊“交換”按鈕來反復切換術語標題和術語說明兩個部分。下面是全部的源代碼,我們先分析一下正則表達式 /(<td>.+?<//td>)(<td>.+?<//td>)/m 。Javascript中,正則表達式的內容是用兩個“/”夾起來的,當正則表達式的內容含有“/”時,則要用“/”對它進行轉義。后一個“/”緊跟著的是正則表達式選項,總共有三個,i、m 和 g,含義分別是忽略大小寫、多行匹配模式和全局匹配(找到所有匹配項,而不是首次找到匹配項即停止)。下面的Javascript代碼中捕獲群的作用我們在前面已經講過,這里就不再重復,但要對它的“/”進行轉義,因為“/”在Javascript屬于元字符范疇。因為我們的正則表達式要應用到html代碼中的每一行,所以最后要指定“m”多行(Multiline)匹配模式;“g”(Global)的作用是讓匹配操作找到所有的匹配內容,而不是在找到第一個匹配后即停止。代碼中,真正實現位置調換的代碼只有var newText = orgText.replace(/(<td>.+?<//td>)(<td>.+?<//td>)/gm, "$2$1")一行代碼,所以說,要是能合理地使用正則表達式,它就能幫助我們輕松地解決很多問題。
-
<html>
-
<head>
-
<title></title>
-
<script
language
=
"JavaScript"
type
=
"text/javascript"
>
-
function swap() {
-
var orgText = document.getElementById(
"testRegion"
).value;
-
// alert(
"原始字符:/n"
+ orgText);
-
var newText = orgText.replace(/(
<td>
.+?<//td
>
)(
<td>
.+?<//td
>
)/gm,
"$2$1"
);
-
// alert(
"調換字符:/n"
+ newText);
-
document.getElementById(
"content"
).innerHTML = newText;
-
document.getElementById(
"testRegion"
).value = newText;
-
}
-
</script>
-
</head>
-
<body>
-
<form
method
=
"get"
>
-
<textarea
id
=
"testRegion"
name
=
"testRegion"
cols
=
"60"
rows
=
"16"
>
-
<
table border=
"
1
">
-
<
tr
>
-
<
td
>
條碼 bar code
<
/td
><
td
>
由一組規則排列的條、空及其對應字符組成的標記,用以表示一定的信息。
<
/td
>
-
<
/tr
>
-
<
tr
>
-
<
td
>
條碼系統 bar code system
<
/td
><
td
>
由條碼符號設計、制作及掃描閱讀組成的自動識別系統。
<
/td
>
-
<
/tr
>
-
<
tr
>
-
<
td
>
條 bar
<
/td
><
td
>
條碼中反射率較低的部分。
<
/td
>
-
<
/tr
>
-
<
tr
>
-
<
td
>
空 space
<
/td
><
td
>
條碼中反射率較高的部分。
<
/td
>
-
<
/tr
>
-
<
/table
>
-
</textarea>
-
<input
type
=
"button"
value
=
"交換"
onclick
=
"swap();"
>
-
</form>
-
<div
id
=
"content"
></div>
-
</body>
-
</html>
四、逆向引用
在捕獲組中,另外一個常用的功能就是逆向引用。它可以引用指定的捕獲組最后一次成功匹配時的匹配內容。我們用 HTML 標記舉個例子,如果我們要想匹配字符串“<title>正則表達式</title>”,常用的方法就是 <title>.+?</title> 。但是這種的寫法有個缺點,就是不夠通用,如果 HTML 被重構成字符串“<h1>正則表達式</h1>”,就得修改正則表達式為 <h1>.+?</h1> ,這樣的做法明顯比較笨拙,有沒有更智能點兒的解決方案呢。還好與捕獲組相關的一個功能就是逆向引用,它的作用就是把捕獲組匹配到的內容記憶下來,然后根據捕獲組所對應的索引號,重新利用最近一次的匹配結果,所以,我們的可以把正則表達式修改為 <([^>]+)>.+?<//1> 。
修改后的正則表達式中用小括號夾起來的就是捕獲組,它對應的編號為1,如果后續還有更多的捕獲組,它們對應的編號就應該是2、3、4 …… 如果要引用一個捕獲組的內容,可以使用語法“/捕獲組編號”。表達式前半部分 <([^>]+)> 可以與起始標簽“<title>”相匹配,并把“title”記憶下來,當匹配到終止標簽的字符“/”后,緊接著就是逆向引用“/1”,它所代表的內容就是“title”。
這里強調一下,捕獲組的內容總是最后一次成功匹配時的結果。比如我們用正則表達式 (/d+/D+) 來匹配字符串“001張三002李四003王五004趙六”,然后用“$1”來進行替換操作,結果只是最后一次匹配的內容 004趙六 。
五、為捕獲組命名
前面已經講過,捕獲組可以用編號進行引用,除此之外,它還可以通過名稱進行引用。比如與字符串“中國 - CN”相匹配的正則表達式 (/w+)/W*(/w+) ,我們想把原來的字符串改寫成“CN - 中國”,就把替換的部分寫成 $2 - $1 。
但“$1”和“$2”僅僅是編號,它不能給代碼閱讀者任何提示性的文字信息,如果能采用“英文縮寫 - 名稱”這樣的形式就好多了,正則表達式支持為捕獲組進行命名,在 Python、PCRE(與 Perl 兼容的正則表達式) 和 PHP 中,語法通常為(?P<GroupName>Pattern),但在有的正則引擎中可能會使用其它語法形式,如 .Net 中使用(?<GroupName>Pattern),具體使用時先查看一些相關文檔。現在,我們假定在 .Net 環境下把“CN - 中國”做一個國家代碼和國家名稱的交替操作,把原來的 (/w+)/W(/w+) 改成 (?<country_code>/w+)/W+(?<country_name>/w+) ,然后替換使用 ${country_name} - ${country_code} ,最后得到的結果就是 中國 - CN 。注意,在為捕獲組命名的時候盡量采用大多數編程語言中所定義的變量命名規則,否則可能會出現無法正常解析正則表達式的錯誤。
六、取消捕獲組
利用正則表達式的捕獲組能極大地提高靈活性,使字符串操作更加方便。但事物總是具有兩面性,捕獲組的缺點就是消耗更多的資源來換取它的靈活性。當我們并不想使用逆向引用時,是不需要捕獲組記憶任何東西的,這種情況下就可以利用 (?:nocapture) 語法來主動地告訴正則表達式引擎,不要把圓括號的內容當作捕獲組,以便提高效率。
這樣,正則表達式 abc(?:/w+) 就不會對圓括號里面的內容進行逆向引用了,而且它的執行速度也要快于創建捕獲組時的速度。
七、捕獲組應用
再次回到本章最初的例子中,我們先分析下正則表達式 (/b/w{3,}/b)(.*?/b/1/b){4,} 。首先是第一個捕獲組 (/b/w{3,}/b) ,它代表的是一個單詞,兩邊是詞界,中間是一個至少包含三個字母的單詞;后面的是 (.*?/b/1/b){4,} ,其中“/1”是對第一個捕獲組的引用,而且重復的次數至少是四次。
想要找出段落中出現次數最多的詞,首先將語句中的冠詞、助詞、代詞等內容用表達式 /b(the|they|them)/b 一次性去除,對于不同的文章,我們可以選定一些特定詞的詞作為要去除的對象。接下來,我們將表達式中控制匹配次數的 {4,} 設置為一個較大的數字,比如 {10,} ,然后逐次減一來找到出現頻度最高的詞,當使用“4”來嘗試時,我們就可以找到單詞“light”了。此時,如果我們把“light”全部替換掉,就可以利用表達式繼續查找下一個高頻度詞了。
<!-- InstanceEndEditable -->
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
