快速入門
教學
工具和語言
範例
參考
書籍評論
Regex 教學
簡介
目錄
特殊字元
不可列印字元
Regex 引擎內部
字元類別
字元類別減法
字元類別交集
簡寫字元類別
錨點
字詞邊界
交替
選用項目
重複
群組和擷取
反向參照
反向參照,第 2 部分
命名群組
相對反向參照
分支重設群組
自由間距和註解
Unicode
模式修改器
原子群組
獨佔量詞
前瞻和後顧
環顧,第 2 部分
讓文字不符合配對
條件
平衡群組
遞迴
子常式
無限遞迴
遞迴和量詞
遞迴和擷取
遞迴和反向參照
遞迴和回溯
POSIX 方括號表示式
零長度配對
繼續配對
本網站的更多內容
簡介
正規表示式快速入門
正規表示式教學
替換字串教學
應用程式和語言
正規表示式範例
正規表示式參考
替換字串參考
書籍評論
可列印 PDF
關於本網站
RSS Feed 和部落格
RegexBuddy—Better than a regular expression tutorial!

測試字串的同一部分以符合多個需求

環顧,在 前一個主題 中有詳細介紹,是一個非常強大的概念。很不幸地,初學正規表示式的人常常沒有善用它,因為環顧有點令人困惑。令人困惑的地方在於環顧是零長度。因此,如果你有一個 regex,其中前瞻後面接著另一段 regex,或後顧前面接著另一段 regex,那麼 regex 會兩次掃描字串的一部分。

一個更實際的範例可以讓這點更清楚。假設我們想要找一個長度為六個字母且包含三個連續字母 cat 的字。實際上,我們可以在不使用環顧的情況下找到它。我們只要指定所有選項並使用 交替 將它們組合在一起:cat\w{3}|\wcat\w{2}|\w{2}cat\w|\w{3}cat。很簡單吧。但是如果你想要在長度介於 6 到 12 個字母之間的字中,找出包含「cat」、「dog」或「mouse」的字,這種方法就會變得難以使用。

環顧救援

在這個範例中,我們基本上有兩個成功配對的要求。首先,我們想要一個長度為 6 個字母的字。其次,我們找到的字必須包含「cat」這個字。

使用 \b\w{6}\b 就可以輕鬆配對一個 6 個字母的字。配對一個包含「cat」的字也一樣容易:\b\w*cat\w*\b

將這兩者結合起來,我們得到:(?=\b\w{6}\b)\b\w*cat\w*\b。很簡單!以下是它的運作方式。在字串中嘗試正規表示式的每個字元位置時,引擎會先嘗試正向先視中的正規表示式。這個子正規表示式,因此先視,僅在字串中當前字元位置位於字串中 6 個字母字的開頭時才會配對。如果不是,先視會失敗,而引擎會從字串中下一個字元位置的開頭繼續嘗試正規表示式。

先視的長度為零。因此,當先視中的正規表示式找到 6 個字母的字時,字串中的當前位置仍然位於 6 個字母字的開頭。正規表示式引擎會在此位置嘗試正規表示式的其餘部分。由於我們已經知道可以在當前位置配對 6 個字母的字,因此我們知道 \b 會配對,而第一個 \w* 會配對 6 次。然後,引擎會 回溯,減少 \w* 配對的字元數,直到可以配對 cat。如果無法配對 cat,引擎別無選擇,只能從正規表示式的開頭重新開始,在字串中的下一個字元位置。這位於我們剛剛找到的 6 個字母字的第二個字母,先視會在此失敗,導致引擎逐字元前進,直到下一個 6 個字母字。

如果 cat 可以成功配對,第二個 \w* 會消耗 6 個字母單字中剩下的字母(如果有)。之後,正規表示式中的最後一個 \b 保證會配對到第二個 lookahead 中的 \b 所配對的位置。我們的雙重需求正規表示式已成功配對。

最佳化我們的解決方案

雖然以上的正規表示式運作良好,但它並非最佳的解決方案。如果你只是在文字編輯器中進行搜尋,這並不成問題。但是,如果你要將這個正規表示式重複使用,或者在開發的應用程式中使用在大量的資料上,最佳化是很好的做法。

如果你仔細檢查正規表示式,並追蹤正規表示式引擎如何套用它,就像我們在上面所做的一樣,你可以自己找出這些最佳化。第三個且最後一個 \b 保證會配對。由於 字詞邊界 是零長度,因此不會改變正規表示式引擎所回傳的結果,我們可以將它們移除,留下:(?=\b\w{6}\b)\w*cat\w*。雖然最後一個 \w* 也保證會配對,但我們無法將它移除,因為它會將字元加入正規表示式配對中。請記住,lookahead 會捨棄它的配對,因此它不會影響正規表示式引擎所回傳的配對。如果我們省略 \w*,產生的配對將會是包含「cat」的 6 個字母單字的開頭,直到「cat」包含在內,而不是整個單字。

但是,我們可以最佳化第一個 \w*。就目前的情況,它將會配對 6 個字母,然後回溯。但是,我們知道在成功的配對中,「cat」之前最多只有 3 個字母。因此,我們可以最佳化為 \w{0,3}。請注意,將星號設為惰性並不足以最佳化。惰性星號會較快找到成功的配對,但是如果 6 個字母的單字不包含「cat」,它仍然會導致正規表示式引擎嘗試在最後兩個字母、最後一個字母,甚至在 6 個字母單字之後的一個字元中配對「cat」。

因此,我們有 (?=\b\w{6}\b)\w{0,3}cat\w*。最後一個微小的最佳化涉及第一個 \b。由於它本身是零長度,因此不需要將它放在 lookahead 中。因此,最後的正規表示式為:\b(?=\w{6}\b)\w{0,3}cat\w*

您也可以將最後的 \w* 替換為 \w{0,3}。但這不會有任何差別。先行斷言已經檢查我們是否位於 6 個字母的字詞,而 \w{0,3}cat 已經比對了該字詞的 3 到 6 個字母。我們是否以 \w*\w{0,3} 結束正規表示式並不重要,因為無論如何,我們都會比對所有剩下的字詞字元。由於產生的比對結果和找到它的速度相同,我們不妨使用較容易輸入的版本。

更複雜的問題

那麼,您會使用什麼來找出任何介於 6 到 12 個字母長,且包含「cat」、「dog」或「mouse」的字詞?我們再次有兩個需求,我們可以使用先行斷言輕鬆地將它們合併:\b(?=\w{6,12}\b)\w{0,9}(cat|dog|mouse)\w*。一旦您掌握訣竅,這非常容易。這個正規表示式也會將「cat」、「dog」或「mouse」放入第一個反向參照。