C#正規表示Group、Lookahead應用

  • 310
  • 0

C#正規表示Group、Lookahead應用

一個字串由小寫英文+數字組成,要將英文字中的的數字加上錢字號($),ex:"a123bkr45" => "a$123bkr45"
如果使用Regex.Replace的Replace(String, String, String)多載該如何做?
此多載的第3個參數可使用[錢字號+數字]代表要替換的Group Value,如$1代表Group[1].Value,至於要換上錢字號可用$$表示

首先想到的pattern是 @"[a-z]([\d]+)[a-z]",要匹配的數字做為一個Group,之後取代時在前面加上錢字號即可

Regex.Matches("a123bkr45", @"[a-z]([\d]+)[a-z]")

看似可以匹配了,但是在取代時就遇到了一個問題:這裡匹配到的是"a123b",如果直接取代會將123前後的a、b一起被取代了,最終得到"$123kr45"

該如何只針對Group[1]也就是123進行取代?

此時將pattern改為 @"([a-z])([\d]+)([a-z])",有了3個額外的Group,數字、數字前後的英文都自成一個Group;而Replace的地方改為"$1$$$2$3",也就是在Group[2](數字的Group)前方加上錢字號
看似只有替換了中間的123,但其實還是整個Match的部分都替換了,只是Group[1]、Group[3]的位置與值都保持不變才有此效果

Regex.Matches("a123bkr45", @"([a-z])([\d]+)([a-z])")

題外話Replace時使用多個Group也可以進行一些字串操作的應用
如 Regex.Replace(s, @"([\S]+)(\s)([\S]+)", "$3$2$1") 可以得到將空格前後的字串兩兩交換位置的效果

這樣看似好像可以了,但接下來換了一個測試案例"a123bkr45z67ac"
再檢查一下匹配,最後的67根本沒被匹配到,替換時最後的67自然也沒被替換為$67

仔細看了下這個匹配結果,應該是67前的z被45匹配掉了,之後匹配不了67前的[a-z]

這時使用Lookahead、Lookbehind:

Lookahead
(?=subexpression)
(?!subexpression)

Lookbehind
(?<=subexpression)
(?<!subexpression)

Lookbehind部分:(?<=subexpression)X,可當作X的前方要有符合subexpression的
Lookahead部分:X(?=subexpression),可當作X的後方要有符合subexpression的
至於!的語法代表"沒有"、"不可有"

Regex.Matches("a123bkr45z67ac", @"(?<=[a-z])([\d]+)(?=[a-z])")

再次調整了語法,可以理解為:要找個數字,且該數字的前後都要有個英文
看似有3個額外的Group,但其實此時前後的英文不會參與匹配,Group只有數字的,這點可以從LinqPad的結果中看出

既然現在英文不參與匹配了,Replace語法也跟著修改

Regex.Replace("a123bkr45z67ac", @"(?<=[a-z])([\d]+)(?=[a-z])", "$$$1")

終於得到了字串 a$123bkr$45z$67ac

 

參考:

https://docs.microsoft.com/en-us/dotnet/standard/base-types/backtracking-in-regular-expressions