正則之反向引用

 

1        概述

捕獲組捕獲到的內容,不僅可以在正則表達式外部通過程序進行引用,也可以在正則表達式內部進行引用,這種引用方式就是反向引用。要了解反向引用,首先要了解捕獲組,關於捕獲組,參考 正則基礎之——捕獲組(capture group)

反向引用的作用通常是用來查找或限定重複、查找或限定指定標識配對出現等等。

對於普通捕獲組和命名捕獲組的引用,語法如下:

普通捕獲組反向引用:\k<number>,通常簡寫爲\number

命名捕獲組反向引用:\k<name>或者\k'name'

普通捕獲組反向引用中number是十進制的數字,即捕獲組的編號;命名捕獲組反向引用中的name爲命名捕獲組的組名。

2       反向引用匹配原理

捕獲組(Expression)在匹配成功時,會將子表達式匹配到的內容,保存到內存中一個以數字編號的組裏,可以簡單的認爲是對一個局部變量進行了賦值,這時就可以通過反向引用方式,引用這個局部變量的值。一個捕獲組(Expression)在匹配成功之前,它的內容可以是不確定的,一旦匹配成功,它的內容就確定了,反向引用的內容也就是確定的了。

反向引用必然要與捕獲組一同使用的,如果沒有捕獲組,而使用了反向引用的語法,不同語言的處理方式不一致,有的語言會拋異常,有的語言會當作普通的轉義處理。

2.1     從一個簡單例子說起

源字符串:abcdebbcde

正則表達式:([ab])\1

對於正則表達式“([ab])\1”,捕獲組中的子表達式“[ab]”雖然可以匹配“a”或者“b”,但是捕獲組一旦匹配成功,反向引用的內容也就確定了。如果捕獲組匹配到“a”,那麼反向引用也就只能匹配“a”,同理,如果捕獲組匹配到的是“b”,那麼反向引用也就只能匹配“b”。由於後面反向引用“\1”的限制,要求必須是兩個相同的字符,在這裏也就是“aa”或者“bb”才能匹配成功。

考察一下這個正則表達式的匹配過程,在位置0處,由“([ab])”匹配“a”成功,將捕獲的內容保存在編號爲1的組中,然後把控制權交給“\1”,由於此時捕獲組已記錄了捕獲內容爲“a”,“\1”也就確定只有匹配到“a”才能匹配成功,這裏顯然不滿足,“\1”匹配失敗,由於沒有可供回溯的狀態,整個表達式在位置0處匹配失敗。

正則引擎向前傳動,在位置5之前,“([ab])”一直匹配失敗。傳動到位置5處時,,“([ab])”匹配到“b”,匹配成功,將捕獲的內容保存在編號爲1的組中,然後把控制權交給“\1”,由於此時捕獲組已記錄了捕獲內容爲“b”,“\1”也就確定只有匹配到“b”才能匹配成功,滿足條件,“\1”匹配成功,整個表達式匹配成功,匹配結果爲“bb”,匹配開始位置爲5,結束位置爲7。

擴展一下,正則表達式“([a-z])\1{2}”也就表達連續三個相同的小寫字母。

2.2     一個複雜例子的分析

詳細的分析討論參考:正則表達式正向預搜索的問題

源字符串:aaa bbbb ffffff 999999999

正則表達式:(\w)((?=\1\1\1)(\1))+

測試代碼:

string test = "aaa bbbb ffffff 999999999";

Regex reg = new Regex(@"(\w)((?=\1\1\1)(\1))+");

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

      richTextBox2.Text += "匹配結果:" + m.Value.PadRight(12, ' ') + "匹配開始位置:" + m.Index + "\n";

}

//輸出

匹配結果:bb          匹配開始位置:4

匹配結果:ffff        匹配開始位置:9

匹配結果:9999999     匹配開始位置:16

匹配結果分析:

正則表達式(\w)((?=\1\1\1)(\1))+從匹配結果上分析,其實就等價於 (\w)(\1)*(?=\1\1\1)(\1) ,這個會相對好理解一些,下面討論下分析過程。

因爲“+”等價於“{1,}”,表示至少匹配1次,下面把子表達式“((?=\1\1\1)(\1))+”展開來看下規律,下表中的“次數”表示子表達式“((?=\1\1\1)(\1))+”匹配成功的次數 。

次數

等價表達式

1

(\w)((?=\1\1\1)(\1))

2

(\w)((?=\1\1\1)(\1))((?=\1\1\1)(\1))

3

(\w)((?=\1\1\1)(\1))((?=\1\1\1)(\1))((?=\1\1\1)(\1))

如果最後一個“((?=\1\1\1)(\1))”匹配成功,那麼中間的“((?=\1\1\1)(\1))”一定可以匹配成功,所以中間的限制條件(?=\1\1\1)就沒有意義了,這時就可以簡寫爲“(\1)”,也就是

次數

等價表達式

1

(\w)((?=\1\1\1)(\1))

2

(\w)(\1)((?=\1\1\1)(\1))

3

(\w)(\1)(\1)((?=\1\1\1)(\1))

可以歸納爲等價於

(\w)(\1)*((?=\1\1\1)(\1))

因爲“((?=\1\1\1)(\1))”開始和結尾的()原來是用作量詞+修飾範圍的,這裏已經沒有什麼意義了,所以表達式最後可以歸納爲等價於

(\w)(\1)*(?=\1\1\1)(\1)

分析這個表達式就容易多了。“(\w)”匹配一個字符,佔一位,“\1”是對“\w”匹配內容的引用,“(\1)*”可以匹配0到無窮多個“(\w)”匹配到的字符,“(?=\1\1\1)(\1)”只佔一位,但是“(?=\1\1\1)”要求所在位置右側有三個連續相同的“(\w)”匹配到的字符,所以在“(?=\1\1\1)”這個位置右側應該有三個字符,不過只有這個位置右側的一個字符計入最後的匹配結果,最後兩個只作爲限制條件,不計入最後的匹配結果 。

以“999999999”爲例,第一個“9”由“(\w)”匹配,第二到第六個“9”由“(\1)*”來匹配,第七個“9”由“(?=\1\1\1)(\1)”中最後的“(\1)”來匹配,而第七、八、九這三個“9”是用來保證滿足“(?=\1\1\1)”這個條件的。

2.3     反向引用的編號

對於普通捕獲組的反向引用,是通過捕獲組的編號來實現的,那麼對於一些可能存在歧義的語法又是如何解析的呢?對於正則表達式

([ab])\10

這裏的“\10”會被解析成第10個捕獲組的反向引用,還是第1個捕獲組的反向引用加一個普通字符“0”呢?不同語言的處理方式是不一樣的。

string test = "ab0cdebb0cde";

richTextBox2.Text = Regex.Match(test, @"([ab])\10").Value;

在.NET中,以上測試代碼輸出爲空,說明這裏的“\10”被解析成第10個捕獲組的反向引用,而這個表達式中是不存在第10個捕獲組的,所以匹配結果爲空。

<script type="text/javascript"> 
var str = "ab0cdebb0cde";
var reg = /([ab])\10/;
var arr = str.match(reg);
if(arr != null)
{
    document.write(arr[0]);
}
</script>

/*--------輸出--------

bb0

*/

而在JavaScript中,由於瀏覽器解析引擎的不同,得到的結果也不一樣,以上爲IE下是可以得到匹配結果“bb0”,說明在IE的瀏覽器引擎中,“\10”被解析成第1個捕獲組的反向引用加一個普通字符“0”。而在Firefox、Opera等瀏覽器中,得到的結果爲空,說明“\10”被解析成第10個捕獲組的反向引用,而這個表達式中是不存在第10個捕獲組的。

string test = "ab0cdebb0cde";

richTextBox2.Text = Regex.Match(test, @"([ab])\10", RegexOptions.ECMAScript).Value;

/*--------輸出--------

bb0

*/

而在.NET中,如果正則表達式加了RegexOptions.ECMAScript參數,則這裏的“\10”被解析成第1個捕獲組的反向引用加一個普通字符“0”。

至於正則表達式中確實有10個以上的捕獲組時,“\10”的具體意義留給有興趣的讀者去測試了,因爲在實際應用當中,如果你的正則表達式中用到了10個以上捕獲組,而同時又用到了第10個以上捕獲組的反向引用時,就要注意分析一下,你的正則是否需要進行優化,甚至於這裏是否適合使用正則表達式了。

出於對現實應用場景的分析,第10個以上捕獲組的反向引用幾乎不存在,對它的研究通常僅存在於理論上。而對於10個以內捕獲組反向引用後面還有數字,容易造成混淆的情況,可以通過非捕獲組來解決。

([ab])\1(?:0)

這樣就可以明確,是對第1個捕獲組的反向引用,後面跟一個普通字符“0”。也就不會產生混淆了。

string test = "ab0cdebb0cde";

richTextBox2.Text = Regex.Match(test, @"([ab])\1(?:0)").Value;

/*--------輸出--------

bb0

*/

而事實上,即使是這樣用的場景也非常少,至今爲止,只在日期正則表達式中用到過。

^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))$

這一節討論的內容,瞭解一下就可以了,在實際應用當中,如果遇到,注意一下不要出現混淆而導致匹配結果錯誤就可以了。

3       反向引用應用場景分析

反向引用的作用通常是用來查找或限定重複、查找或限定指定標識配對出現等等。以下以實例進行場景分析及應用講解。

3.1     查找重複

查找重複通常的應用場景是查找或驗證源字符串中,是否有重複單詞、重複項等等。

3.1.1  驗證數字元素重複項

需求描述:

驗證源字符串中以“,”分隔的數字是否有重複項。

代碼實現:

string[] test = new string[] { "1,2,3,123,32,13", "12,56,89,123,56,98", "8,2,9,10,38,29,2,9", "8,3,9,238,93,23" };

Regex reg = new Regex(@"\b(\d+)\b.*?\b\1\b");

foreach (string s in test)

{

     richTextBox2.Text += "源字符串: " + s.PadRight(20, ' ') + "驗證結果: " + reg.IsMatch(s) + "\n";

}

/*--------輸出--------

源字符串: 1,2,3,123,32,13     驗證結果: False

源字符串: 12,56,89,123,56,98  驗證結果: True

源字符串: 8,2,9,10,38,29,2,9  驗證結果: True

源字符串: 8,3,9,238,93,23     驗證結果: False

*/

源字符串的規則比較明確,就是用“,”分隔的數字,類似於這種查找是否有重複的需求,最簡單的就是用反向引用來解決了。

由於要驗證的是用“,”分隔的元素的整體是否有重複,所以“(\d+)”兩側的“\b”就是必須的,用來保證取到的數字子串是一個元素整體,而不是“123”中的“1”,當然,這裏前後兩個“\b”分別換成“(?<!\d)”和“(?!\d)”是一個效果,可能意義上更明確。後面的兩個“\b”也是一樣的作用。

3.1.2  驗證連續數字是否有重複

參考 問兩個正則表達式

需求描述:

數據:

1985aaa1985bb

bcae1958fiefadf1955fef

atijc1944cvkd

df2564isdjfef2564d

實現1:匹配第一次出現的四個數字.然後後面也存在這四個數字的

如:

1985aaa1985bb

第一次出現的四個數字是1985.然後後面也存在這四個數字,所以這個匹配

bcae1958fiefadf1955fef

第一次出現的四個數字是1958.然後後面不存在這四個數字.所以不匹配

-----

所以實現1.應該匹配

1985aaa1985bb

df2564isdjfef2564d

代碼實現:

//如果是驗證第一個出現的連續4個數字是否有重複

string[] test = new string[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };

Regex reg = new Regex(@"^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1");

foreach (string s in test)

{

     richTextBox2.Text += "源字符串:  " + s.PadRight(25, ' ') + "驗證結果:  " + reg.IsMatch(s) + "\n";

}

/*--------輸出--------

源字符串:  1985aaa1985bb            驗證結果:  True

源字符串:  bcae1958fiefadf1955fef   驗證結果:  False

源字符串:  atijc1944cvkd            驗證結果:  False

源字符串:  df2564isdjfef2564d       驗證結果:  True

源字符串:  abc1234def5678ghi5678jkl 驗證結果:  False

*/

由於需求要求驗證第一次出現的四個數字是否有重複,所以這裏需要用“^(?:(?!\d{4}).)*(\d{4})”來保證捕獲組取得的是第一次出現的四個數字。

這樣寫可能有些複雜,可讀性較差,但這裏需要用這種順序環視結合貪婪模式,來達到匹配第一次出現的四個數字的目的,而不能使用非貪婪模式.

對於使用非貪婪模式的正則“^.*?(\d{4})(?:(?!\1).)*\1”,可以看一下它匹配的結果。

string[] test = new string[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };

Regex reg = new Regex(@"^.*?(\d{4})(?:(?!\1).)*\1");

foreach (string s in test)

{

     richTextBox2.Text += "源字符串:  " + s.PadRight(25, ' ') + "驗證結果:  " + reg.IsMatch(s) + "\n";

}

/*--------輸出--------

源字符串:  1985aaa1985bb            驗證結果:  True

源字符串:  bcae1958fiefadf1955fef   驗證結果:  False

源字符串:  atijc1944cvkd            驗證結果:  False

源字符串:  df2564isdjfef2564d       驗證結果:  True

源字符串:  abc1234def5678ghi5678jkl 驗證結果:  True

*/

是的,最後一項的驗證結果也是“True”,爲什麼會這樣?當捕獲組“(\d{4})”匹配到“1234”時,由於“1234”沒有重複,所以後面的子表達式匹配失敗,此時“.*?”會進行回溯,放棄當前狀態,繼續向前匹配,直到它匹配到“5678”前的“f”,由捕獲組“(\d{4})”匹配到“5678”,後面的子表達式可以匹配成功,報告整個表達式匹配成功。

NFA引擎在有可供回溯的狀態時,會一直嘗試直到所有可能都嘗試失敗後才報告失敗。上例中非貪婪模式在繼續嘗試時是可以找到匹配成功的位置的,而採用貪婪模式的正則“^(?:(?!\d{4}).)*(\d{4})”,由於“^(?:(?!\d{4}).)*”匹配到的內容不可能是連續的四個數字,所以無論怎麼回溯,接下來的“(\d{4})”都不可能匹配成功,一直回溯到起始位置“^”,報告整個表達式匹配失敗。

而後面的順序環視+貪婪模式子表達式“(?:(?!\1).)*”則不存在以上問題,所以在源字符串比較簡單時可以寫作“.*?”,不會影響匹配結果。

而對於驗證任意位置是否存在四個重複數字,則不需要加起始位置的限定。

//如果是驗證任意位置出現的連續4個數字是否有重複,可以用我38樓的正則

string[] test = new string[] { "1985aaa1985bb", "bcae1958fiefadf1955fef", "atijc1944cvkd", "df2564isdjfef2564d", "abc1234def5678ghi5678jkl" };

Regex reg = new Regex(@"(\d{4})(?:(?!\1).)*\1");

foreach (string s in test)

{

     richTextBox2.Text += "源字符串:  " + s.PadRight(25, ' ') + "驗證結果:  " + reg.IsMatch(s) + "\n";

}

/*--------輸出--------

源字符串:  1985aaa1985bb            驗證結果:  True

源字符串:  bcae1958fiefadf1955fef   驗證結果:  False

源字符串:  atijc1944cvkd            驗證結果:  False

源字符串:  df2564isdjfef2564d       驗證結果:  True

源字符串:  abc1234def5678ghi5678jkl 驗證結果:  True

*/

3.2     限定指定標識配對

相對於查找重複來說,查找或指定標識配對出現這種應用場景要更多一些。尤其是對於HTML的處理中,這種應用更普遍。

3.2.1  限定標點配對

由於HTML語言的不規範性,導致以下三種寫法可以被解析。

1.   <a href=www.csdn.net>CSDN</a>

2.   <a href='www.csdn.net'>CSDN</a>

3.   <a href="www.csdn.net">CSDN</a>

而這對於一些需要進行字符串解析的應用,造成很大的麻煩。在提取鏈接時,雖然兩側都用“[‘”]?”通常也可以得到正確結果,卻不如用反向引用來得嚴謹、方便。

Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""]?)(?<url>[^""'\s>]*)\1[^>]*>(?<text>(?:(?!</a>).)*)</a>");

MatchCollection mc = reg.Matches(yourStr);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Groups["url"].Value + "\n";

     richTextBox2.Text += m.Groups["text"].Value + "\n";

}

/*--------輸出--------

www.csdn.net

CSDN

www.csdn.net

CSDN

www.csdn.net

CSDN

*/

以下可以正確解析出三種形式的HTML代碼中的鏈接和文本,下面把正則改一下

Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""])?(?<url>[^""'\s>]*)\1[^>]*>(?<text>(?:(?!</a>).)*)</a>");

看到區別了嗎?只是把“([‘””]?)”改成了“([‘””])?”,結果會怎麼樣呢?

Regex reg = new Regex(@"(?is)<a(?:(?!href=).)*href=(['""])?(?<url>[^""'\s>]*)\1[^>]*>(?<text>(?:(?!</a>).)*)</a>");

MatchCollection mc = reg.Matches(yourStr);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Groups["url"].Value + "\n";

     richTextBox2.Text += m.Groups["text"].Value + "\n";

}

/*--------輸出--------

www.csdn.net

CSDN

www.csdn.net

CSDN

*/

結果只取到了兩組數據。這是因爲對於情況1的HTML字符串,在“([‘””]?)”這種情況下,捕獲組雖然匹配到的只是一個位置,但畢竟是匹配成功了,所以可以用“\1”進行反向引用;而改成“([‘””])?”,捕獲組根本就沒有進行匹配,所以也就無法進行反向引用。

當然,對於HTML來說,還有一些比較複雜的情況,如

<a href="javascript:alert(1 > 2)"/>

這種複雜情況涉及到的場景比較少,通常應用可以不予以考慮,否則考慮的場景太複雜,會影響匹配效率。寫正則的一個一般原則就是,適用就好。這種場景如果遇到,需求根據具體情況,是否需要提取等進行分析,根據分析結果不同,寫出的正則也是不一樣的。

3.2.2          限定標籤配對

這種應用一般是在取某幾個特定標籤,或是動態生成正則表達式時用到。

需求描述:

刪除<script…>…</script>與<style…>…</style>標籤及其中間的內容。

代碼實現:

Regex reg = new Regex(@"(?is)<(script|style)\b[^>]*>(?(?!\1\b).)*</\1>");

string result = reg.Replace(yourStr, "");

因爲這裏要刪除的標籤不止一個,所以事先無法確定是哪個標籤,需要用到反向引用來限定標籤的配對。

當然,對於標籤有嵌套的情況,就要用到平衡組了。可以參考 .NET正則基礎之——平衡組

3.2.3  取配對標籤中的內容

需求描述:

[id]5554323[id!][destid]10657302023180404[destid!][srcterminalid]13518841197[srcterminalid!][msgcontent]好的[msgcontent!][receivetime]20090409165217[receivetime!]

源字符串中標籤成對出現,無嵌套,分別提取標籤和對應的內容。

代碼實現:

string test = "[id]5554323[id!][destid]10657302023180404[destid!][srcterminalid]13518841197[srcterminalid!][msgcontent]好的[msgcontent!][receivetime]20090409165217[receivetime!]";

Regex reg = new Regex(@"(?s)\[([^\]]+)\]((?:(?!\[\1).)*)\[\1!\]");

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

     richTextBox2.Text += "Tag: " + m.Groups[1].Value.PadRight(20, ' ') + "Content: " + m.Groups[2].Value + "\n";

}

/*--------輸出--------

Tag: id                  Content: 5554323

Tag: destid              Content: 10657302023180404

Tag: srcterminalid       Content: 13518841197

Tag: msgcontent          Content: 好的

Tag: receivetime         Content: 20090409165217

*/

這種需求通常是由捕獲組匹配到一個標籤,然後向後匹配,直到與之配對的標籤外爲止,根據源字符串的特點,中間可以使用非貪婪模式,也可以使用順序否定環視+貪婪模式。

3.3     反向引用的綜合應用

3.3.1   12位數字,其中不能出現6位連續相同數字

需求描述:

只允許12位數字,並且其中不能出現6位連續相同數字。

例如,123456789012是允許的,而123333334567是不允許的。

正則表達式:^(?:([0-9])(?!\1{5})){12}$

類似這種需要判定是否有連續相同元素的需求,其實也是驗證重複,也要用到反向引用。

說下分析過程,需求分解一下:

1、 一個數字

2、 它後面不能連續出現5個與它相同的數字

3、 滿足以上兩條的字符一共12個

那麼根據需求分解寫出相應的正則:

1、([0-9])

2、(?!\1{5})

3、.{12}

將以上三個分解後得出的正則,按需求邏輯關係,組合一下:

(([0-9])(?!\1{5})){12}

由於是驗證整個字符串的規則,所以開始和結束標識“^”和“$”是少不了的,不需要用捕獲組的地方,用非捕獲代替,也就成了最後滿足需求的正則:

^(?:([0-9])(?!\1{5})){12}$

其實這個例子的分析過程,也是一些正則問題解析的通用過程,先把複雜的需求由整到零的分解,再各個實現,然後把實現的正則由零到整,考慮一下相互間的邏輯關係,基本上就可以得出正確的正則表達式了。

3.3.2   A-Z以內不重複的10個字母

需求描述:A-Z以內不重複的10個字母

正則表達式1:^(?:([A-Z])(?!.*?\1)){10}$

正則表達式2:^(?:([A-Z])(?=((?!\1).)*$)){10}$

這個需求與上一個需求類似,分析過程也差不多。其實這個問題如果用正則來實現,思路是非常清晰的 。

首先因爲是驗證規則,所以“^”和“$”是必不可少的,分別匹配開始和結束的位置 。

然後是10個字母,那麼([A-Z]){10},合起來就是^([A-Z]){10}$ 。

最後就是加一個規則,字母不能重複 。

如何保證不能重複,必然是用到反向引用 ,(一個字母)後面任意一個字母不能與這個字母重複,這樣實現起來就有兩種方式,當然,實質都是一樣的

實現方式一:^(?:([A-Z])(?!.*?\1)){10}$

實現方式二:^(?:([A-Z])(?=(?:(?!\1).)*$)){10}$

在這個需求當中,由於可能出現的源字符串不會太長,也不會太複雜,所以這兩個正則表達式在匹配效率上不會有明顯的差異。

解釋一下正則的含義,先解釋一下方式一的正則:

^(?:([A-Z])(?!.*?\1)){10}$

^”和“$”分別匹配開始和結束位置,“{10}”爲量詞,表示修飾的子串重複10次。

(?:Expression)”是非捕獲組,目的是不將“()”內的“Expression”匹配的內容保存到內存中,之所以要這樣用,是因爲後面的反向引用使用的是“\1”,如果不用非捕獲組,那麼“([A-Z])”就是編號爲2的捕獲組,後面的“\1”就要換成“\2”,來引用第二個捕獲組,替換後對匹配結果當然不會有什麼影響,但由於由“(([A-Z])(?!.*?\1))”捕獲的內容我們並不關心,所以還是用非捕獲組,可以提升匹配效率。

([A-Z])”就是匹配A到Z之間的任意一個字母,並保存匹配結果到捕獲組中。

(?!.*?\1)”順序環視,它是零寬度的,雖然進行匹配,但不保存匹配結果,可以理解爲它就是在所在位置的右側附加了一個條件,用在這裏表示,它所在位置的右側,不管間隔多少個字符,都不能出現之前匹配到的那個字符,也就是不能有重複的字母出現。

(?:([A-Z])(?!.*?\1)){10}”就是匹配到這樣一個字符 :

1、它首先是一個字母;

2、然後這個字母的右側間隔任意多個字符,不能再出現同樣的字母;

3、最後,符合以上兩條規則的字符,一共有10個。

加上首尾限定字符“^”和“$”,就是滿足需求的正則。

接下來討論一下方式二的正則:

^(?:([A-Z])(?=(?:(?!\1).)*$)){10}$

思路和以及其餘部分子表達式與方式一完全一樣 ,只有“(?=(?:(?!\1).)*$)”這裏不同,這個子表達式表示,它所在位置右側,一直到結尾,都不能是之前匹配到的那個字符。方式一是非貪婪模式的實現,而這個就是貪婪模式的實現。

這裏需要用到順序肯定環視“(?=Expression)”,而不能用非捕獲組“(?:(?:(?!\1).)*$)”,是因爲這裏的表達式不能佔有字符,只能作爲條件存在,由量詞“{10}”修飾的子表達式最終只能匹配一個字符,否則就無法限定長度了。

3.3.3   提取指定單元長度字符串

需求描述 參考 求一正則表達式(c# )

例如有字符串 string str = "w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7",找出有且僅有兩個單元(w+數字作爲一個單元,例如:w1,w2)組成的長度大於等於4個單元的字串(必須包括這兩個單元),這個例子,應輸出:"w2w3w2w3","w4w5w4w5w4w4w5w4","w4w3w4w3","w6w5w6w5w6"

如果找出有且僅有三個單元長度大於等於6個單元的字串,該如何寫正則表達式?

代碼實現:

//第一個需求,兩單元的

string str = "w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7w7w7w5w7";

MatchCollection mc = Regex.Matches(str, @"(?i)(?=(w\d)\1*(w\d))(?:\1|\2){4,}");

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "\n";

}

/*--------輸出--------

bb0w7w7w7w5w7

w2w3w2w3

w4w5w4w5w4w4w5w4

w4w3w4w3

w6w5w6w5w6

w4w7w7w7

*/

//第二個需求,三單元的

string str = "w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7w7w7w5w7";

MatchCollection mc = Regex.Matches(str, @"(?i)(?=(w\d)\1*(w\d)(?:\1|\2)*(w\d))(?:\1|\2|\3){6,}");

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "\n";

}

/*--------輸出--------

bb0w7w7w7w5w7w1

w2w3w2w3w1w3w2

w4w5w4w5w4w4w5w4w2w4

w2w6w5w6w5w6

w4w7w7w7w5w7

*/

這個實例可以認爲是環視和反向引用綜合運用的一個經典實例。主要是用到了環視零寬度,不佔有字符的特性,先由環視來取得規定單元的捕獲組的內容,再通過反向引用來進行實際的匹配。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章