此網站上的更多資訊 |
簡介 |
正則表示式快速入門 |
正則表示式教學 |
替換字串教學 |
應用程式和語言 |
正則表示式範例 |
正則表示式參考 |
替換字串參考 |
書籍評論 |
可列印 PDF |
關於此網站 |
RSS Feed 和部落格 |
本教學課程中較早的主題說明了 正則表示式遞迴 和 正則表示式子常式。在本主題中,「遞迴」一詞是指整個正規表示式的遞迴、擷取群組的遞迴,以及對擷取群組的子常式呼叫。
Perl 和 Ruby 會在遞迴後的正規表示式其餘部分失敗時回溯到遞迴中。它們會視需要嘗試遞迴的所有排列,以允許正規表示式其餘部分比對。PCRE 會將遞迴視為 原子。PCRE 會在遞迴期間正常回溯,但一旦遞迴比對成功,即使正規表示式其餘部分無法比對,它也不會嘗試遞迴的任何進一步排列。結果是 Perl 和 Ruby 可能會找到 PCRE 找不到的正規表示式比對,或者 Perl 和 Ruby 可能會找到不同的正規表示式比對。
考慮 Perl 中的正規表示式 aa$|a(?R)a|a 或 Ruby 2.0 中的等效表示式 aa$|a\g'0'a|a。PCRE 支援兩種語法。讓我們看看 Perl、Ruby 和 PCRE 如何進行此正規表示式的配對程序,其中 aaa 是主旨字串。
第一個選項 aa$ 失敗,因為 錨點 無法在字串中第二和第三個 a 之間配對。嘗試字串開頭的第二個選項,a 配對 a。現在正規表示式引擎進入第一次遞迴。
在遞迴內,第一個選項配對字串中第二和第三個 a。正規表示式引擎退出成功的遞迴。但是現在,正規表示式中 (?R) 或 \g'0' 後面的 a 無法配對,因為正規表示式引擎已經到達字串的結尾。因此,正規表示式引擎必須回溯。這裡是 PCRE 與 Perl 或 Ruby 行為不同的部分。
Perl 和 Ruby 記得在遞迴內正規表示式配對第二個選項,並且有三個可能的選項。Perl 和 Ruby 回溯進入遞迴。遞迴內的第二個選項回溯,將目前的配對縮減為字串中的第一個 a。現在嘗試第三個選項。a 配對字串中的第二個 a。正規表示式引擎再次成功退出相同的遞迴。這次,正規表示式中 (?R) 或 \g'0' 後面的 a 配對字串中的第三個 a。aaa 被發現為整體配對。
另一方面,PCRE 除了匹配字串結尾的 aa 之外,不會記住任何遞迴。PCRE 會在遞迴中進行回溯,將目前的匹配範圍縮小到字串中的第一個 a。但這會讓正規表示式中的第二個選項沒有任何進一步的排列組合可以嘗試。因此,第二個選項開頭的 a 也會進行回溯,將目前的匹配範圍縮小到沒有任何內容。PCRE 會從字串開頭使用第三個選項繼續嘗試匹配,並發現 a 匹配字串開頭的 a。在 PCRE 中,這便是整體匹配。
您可以在 Perl 和 Ruby 中透過新增原子群組來讓遞迴具有原子性。Perl 中的 aa$|a(?>(?R))a|a 和 Ruby 中的 aa$|a(?>\g'0')a|a 與 PCRE 中的原始正規表示式相同。
JGsoft V2 讓您可以選擇遞迴是否具有原子性。原子遞迴的效能較佳,但可能會排除某些匹配或找到不同的匹配,如上所示。JGsoft V2 中的 aa$|a(?P>0)a|a 是原子的。您可以記住這一點,因為此遞迴語法使用尖括號,就像原子群組一樣。您可以使用數字或名稱來取代零,以進行原子子常式呼叫至編號或命名擷取群組。JGsoft V2 中的任何其他遞迴語法都不是原子的。
Boost 有兩種心態。Boost 中整個正規表示式的遞迴是原子的,就像在 PCRE 中一樣。但 Boost 會像 Perl 一樣回溯到子常式呼叫和擷取群組的遞迴中。因此,您可以透過將整個正規表示式包裝到擷取群組中,然後呼叫它來在 Boost 中進行非原子遞迴。
PCRE2 最初的行為就像 PCRE,將所有遞迴和子常式呼叫視為原子。PCRE2 10.30 更改了這一點,試圖更像 Perl,但最終卻像 Boost。PCRE2 10.30 會像 Perl 一樣回溯到子常式呼叫和擷取群組的遞迴中。但 PCRE2 仍然無法回溯到整個正規表示式的遞迴中。在以下範例中,「PCRE」僅表示原始的 PCRE。對於 PCRE2 10.22 及更早版本,請遵循 PCRE 範例。對於 PCRE2 10.30 及更新版本,請遵循 Perl 範例。
關於 遞迴和擷取群組 的主題說明了一個正規表示式,用於比對長度為奇數個字元的 迴文。這個解法看起來很簡單。在 Perl 中,\b(?'word'(?'letter'[a-z])(?&word)\k'letter'|[a-z]?)\b 就可達成目標。量詞 ? 使得比對迴文中間字元的 [a-z] 為選用。在 Ruby 中,我們可以使用 \b(?'word'(?'letter'[a-z])\g'word'\k'letter+0'|[a-z]?)\b,它將相同的量詞新增到解法中,用來指定 反向參照的遞迴層級。在 PCRE 中,Perl 解法仍比對奇數長度的迴文,但無法比對偶數長度的迴文。
讓我們看看這些正規表示式如何比對或無法比對 deed。PCRE 的開頭與 Perl 和 Ruby 相同,就像原始正規表示式一樣。群組「letter」比對 d。在連續三次遞迴期間,群組擷取 e、e 和 d。第四次遞迴失敗,因為沒有字元可以比對。回到第三次遞迴,第一個選項會回溯,而第二個選項比對字串結尾的 d。引擎以成功的比對結束第三次遞迴。回到第二次遞迴,反向參照會失敗,因為字串中沒有字元了。
在這裡,行為出現分歧。Perl 和 Ruby 會回溯進入第三次遞迴,並回溯量詞 ?,這會讓第二個選項變為可選。在第三次遞迴中,第二個選項會放棄字串結尾處比對到的 d。引擎再次退出第三次遞迴,這次是成功比對到長度為 0 的字串。回到第二次遞迴,反向參照仍然會失敗,因為群組儲存了第二次遞迴的 e,但字串中的下一個字元是 d。因此,第一個選項會被回溯,而第二個選項會比對到字串中的第二個 e。第二次遞迴成功退出。
在第一次遞迴中,反向參照再次失敗。群組儲存了第一次遞迴的 e,但字串中的下一個字元是 d。同樣地,Perl 和 Ruby 會回溯進入第二次遞迴,嘗試第二個選項找到長度為 0 的比對。回到第一次遞迴,反向參照現在比對到字串中的第二個 e。引擎成功離開第一次遞迴。回到整體比對嘗試,反向參照比對到字串中的最後一個 d。字首字尾界線成功,找到整體比對。
然而,PCRE 不會回溯進入第三次遞迴。當它回溯第二次遞迴中的第一個選項時,它會回溯越過第三次遞迴。現在,第二次遞迴中的第二個選項會比對到字串中的第二個 e。第二次遞迴成功退出。
在第一次遞迴中,反向參照再次失敗。群組儲存了第一次遞迴的 e,但字串中的下一個字元是 d。同樣地,PCRE 不會回溯進入第二次遞迴,而是立即讓第一次遞迴中的第一個選項失敗。現在,第一次遞迴中的第二個選項會比對到字串中的第一個 e。PCRE 成功退出第一次遞迴。回到整體比對嘗試,反向參照會失敗,因為群組在遞迴之前擷取了 d,而下一個字元是字串中的第二個 e。再次回溯,整體正規表示式比對中的第二個選項現在會比對到字串中的第一個 d。然後,字首字尾界線會失敗。PCRE 沒有找到任何比對。
若要在 PCRE 中比對任何長度的迴文,我們需要一個正規表示式,分別比對偶數個字元和奇數個字元的字詞。自由間距模式讓這個正規表示式更容易閱讀
\b(?'word'
(?'oddword' (?'oddletter' [a-z])(?P>oddword) \k'oddletter' |[a-z])
| (?'evenword'(?'evenletter'[a-z])(?P>evenword)?\k'evenletter')
)\b
基本上,這是兩個原始正規表示式的拷貝與交替組合。第一個交替選項將群組「word」和「letter」重新命名為「oddword」和「oddletter」。第二個交替選項將群組「word」和「letter」重新命名為「evenword」和「evenletter」。呼叫 (?P>evenword) 現在使用問號,而不是交替選項 |[a-z],變成可選的。新的群組「word」結合了兩個群組「oddword」和「evenword」,以便字元邊界仍適用於整個正規表示式。
此正規表示式中的第一個交替選項「oddword」與主題中討論的正規表示式以完全相同的方式,比對出奇數長度的迴文,例如 radar。遞迴和擷取群組。新正規表示式中的第二個交替選項從未嘗試過。
當字串是偶數長度的迴文時,例如 deed,新正規表示式會先嘗試第一個交替選項的所有排列組合。僅在第一個交替選項找不到比對項時,才會嘗試第二個交替選項「evenword」。
第二個替代方案的開頭與原始正規表示式相同。群組「evenletter」比對 d。在連續三次遞迴中,群組擷取 e、e 和 d。第四次遞迴失敗,因為沒有字元可以比對。回到第三次遞迴,正規表示式引擎會注意到遞迴呼叫 (?P>evenword)? 是選用的。它會繼續進行反向參照 \k'evenletter'。反向參照會失敗,因為字串中沒有任何字元。由於遞迴沒有其他替代方案可以嘗試,因此會回溯。群組「evenletter」必須放棄最近的比對,而 PCRE 會退出失敗的第三次遞迴。
在第二次遞迴中,反向參照會失敗,因為擷取群組在該遞迴中比對 e,但字串中的下一個字元是 d。群組會放棄另一個比對,而 PCRE 會退出失敗的第二次遞迴。
回到第一次遞迴,反向參照會成功。群組在該遞迴中比對字串中的第一個 e,而反向參照會比對第二個。PCRE 會退出成功的第一次遞迴。
回到整體比對嘗試中,反向參照會再次成功。群組在整體比對嘗試中比對字串開頭的 d,而反向參照會比對最後一個 d。退出群組「evenword」和「word」,字詞邊界會比對字串的結尾。 deed 是整體比對。
| 快速入門 | 教學 | 工具和語言 | 範例 | 參考 | 書籍評論 |
| 簡介 | 目錄 | 特殊字元 | 不可列印字元 | Regex 引擎內部 | 字元類別 | 字元類別減法 | 字元類別交集 | 簡寫字元類別 | 點 | 錨點 | 字詞邊界 | 交替 | 可選項目 | 重複 | 群組和擷取 | 反向參照 | 反向參照,第 2 部分 | 命名群組 | 相對反向參照 | 分支重設群組 | 自由間距和註解 | Unicode | 模式修改器 | 原子群組 | 所有格量詞 | 前瞻和後顧 | 前瞻和後顧,第 2 部分 | 將文字保留在比對之外 | 條件 | 平衡群組 | 遞迴 | 子常式 | 無限遞迴 | 遞迴和量詞 | 遞迴和擷取 | 遞迴和反向參照 | 遞迴和回溯 | POSIX 方括號表示式 | 零長度比對 | 繼續比對 |
頁面網址:https://regular-expressions.dev.org.tw/recursebacktrack.html
頁面最後更新:2019 年 11 月 22 日
網站最後更新:2024 年 3 月 15 日
版權所有 © 2003-2024 Jan Goyvaerts。保留所有權利。