
“如果一開始你沒有成功,請嘗試,再試一次”這句格言在模式匹配中被形式化,過程稱為回溯.回溯是匹配器檢查所有可能匹配的組合的方式。這篇文章使用 JMP 模式匹配器中的一些秘密功能來公開回溯過程。 下面的單個語句是一個中等複雜的模式匹配。但即使是簡單的匹配(和正則表達式)也會使用回溯。
秘密 1:第 21 行的 Log() 函數在 JMP 日誌窗口中產生輸出。 >> before 它獲取先前匹配的文本並打印它,以及源中從零開始的位置和提供的文本標籤。 這與回溯無關,與調試有關。 你可以使用這個秘密來調試你自己的模式。
秘密 2:PatTest() 有三個參數,即使只使用了一個。 這都是關於回溯的。 第二個參數中的JSL在前向跟踪方向執行,第三個參數中的JSL在回溯方向執行。
秘密 3:PatMatch() 通常會盡其所能優化失敗。 當它發現源字符串中沒有足夠的文本來匹配模式的剩餘最小長度時,它就會停止。 FULLSCAN 選項關閉該優化。 對於此示例,如果沒有 FULLSCAN 選項,回溯似乎會過早停止。
秘密 4:PatFail() 可用於通過強制每個匹配失敗並考慮替代方案來查找所有可能的匹配。
這是代碼和輸出,下面有解釋。
Pat Match(
"EDACGDE", // a contrived string to exercise backtracking
(
Pat Len( 1 ) >> p // match a character, store in p
+
Pat Test(
p > "B", // test
Write( "p=", p, " " ), // advance
Write( "retry\!n" ) // backtrack
)
+
Pat Arb() // skip zero or more characters
+
Pat Len( 1 ) >> q // match another, store in q
+
Pat Test(
p < q & q < "F", // test
Write( "q=", q, " " ), // advance
Write( "retry " ) // backtrack
)
) >> Log( "FOUND" ) // a debugging tool
+
Pat Fail(), // try alternatives
NULL, // no replacement
FULLSCAN // to the bitter end
);
p=E q=D 重試 q=A 重試 q=C 重試 q=G 重試 q=D 重試 q=E 重試 重試
p=D q=A 重試 q=C 重試 q=G 重試 q=D 重試 q=E
1(找到)DACGDE
重試重試
p=重試
p=C q=G 重試 q=D
3(找到)CGD
重試 q=E
3(找到)CGDE
重試重試
p=G q=D 重試 q=E 重試 重試
p=D q=E
5(找到)DE
重試重試
p=E 重試
0
解釋
首先,最後的零。 那是返回碼,它表示匹配失敗。 比賽其實成立四個匹配,但 PatFail() 導致它尋找另一個。 這是意料之中的。
接下來是 Ps 和 Qs。 p 從 E 開始,對 >B 的測試很滿意。 PatArb() 不情願,最初什麼也不跳過。 q 在 E 之後得到 D。 但是這次考試不開心; p 和“F”之間的 q 不正確。 是時候回溯了! 將 D 放入 q 的 LEN(1) 將 D 返回,而 PatArb() 則有機會做一些不同的事情。 這一次它不情願地選擇了一個角色,D。 PatLen(1) 然後將 A 賦予 q。 在第一行結束時,有兩次重試;第二個是p。 下一行顯示 p 獲取第二個字符 D。由於模式匹配沒有錨定,因此作為回溯過程的一部分,允許匹配的開頭在文本中向前浮動;這次比賽繼續進行,PatArb 越來越多,直到第一次成功發生。 >>Log("FOUND") 在日誌中記錄成功,然後 PatFail() 導致回溯繼續。
如果不清楚,您可以嘗試更簡單的測試並使用 >>LOG("tag") 方法對它們進行檢測,以了解正在發生的事情。
在這一點上,應該清楚為什麼這場比賽會持續非常非常長的時間:

雖然您可以看到它不可能成功,因為 aaaa...aaa 的字符串在任何地方都沒有 b,但匹配器必須通過嘗試許多愚蠢的組合來發現這一點:跳過 2 個字符,然後跳過 3... 並跳過 3其次是跳過2 ...等,等等,等等...... 回溯不能修復寫得不好的模式。 那個進度條並不意味著它快完成了。 (沒有理由將 patarb 放在 patrepeat 中,除了看到這個對話框。 Patarb 已經匹配任意數量的字符。)
2017 年 2 月 1 日更新 - 修復新社區的格式。