java正则表达式 n_如何将a^nb^n与Java正则表达式匹配?
答案是,不用說,是!當然,您可以編寫一個Java regex模式來匹配anbn..它使用一個積極的前瞻性斷言,一個嵌套的引用用于“計數”。
這個答案將引導讀者閱讀,而不是立即給出答案。過程得到它。隨著解的緩慢構造,給出了各種提示。在這方面,希望這個答案包含的不僅僅是另一個整潔的regex模式。希望讀者還將學習如何“在regex中思考”,以及如何將各種結構和諧地組合在一起,以便在將來能夠自己派生出更多的模式。
開發解決方案所用的語言將是PHP,因為它簡潔。一旦最終確定了模式,最后的測試將在Java中完成。
步驟1:展望斷言
讓我們從一個簡單的問題開始:我們想要匹配a+在字符串的開頭,但前提是它緊跟在b+..我們可以用^到錨因為我們只想匹配a+沒有b+,我們可以用前瞻斷言(?=…).
下面是我們使用一個簡單測試工具的模式:
function testAll($r, $tests) {
foreach ($tests as $test) {
$isMatch = preg_match($r, $test, $groups);
$groupsJoined = join('|', $groups);
print("$test $isMatch $groupsJoined\n");
}
}
$tests = array('aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb');
$r1 = '/^a+(?=b+)/';
#? ? ? ? ? └────┘
#? ? ? ? ?lookahead
testAll($r1, $tests);
輸出是(如ideone.com所見):
aaa 0
aaab 1 aaa
aaaxb 0
xaaab 0
b 0
abbb 1 a
這正是我們想要的輸出:我們匹配a+,只有當它位于字符串的開頭,并且它的后面緊跟著b+.
教課:您可以在“旁觀者”中使用模式來進行斷言。
步驟2:以向前看的方式捕獲(和f r e-s p a c i n g模式)
現在讓我們說,即使我們不想b+作為比賽的一部分,我們確實想俘獲同樣,由于我們預計有一個更復雜的模式,讓我們使用x改性劑自由間距這樣我們就可以讓正則表達式更易讀了。
在前面的PHP片段的基礎上,我們現在有了以下模式:
$r2 = '/ ^ a+ (?= (b+) ) /x';
#? ? ? ? ? ? ?│? ?└──┘ │
#? ? ? ? ? ? ?│? ? ?1? │
#? ? ? ? ? ? ?└────────┘
#? ? ? ? ? ? ? lookahead
testAll($r2, $tests);
輸出現在是(如ideone.com所見):
aaa 0
aaab 1 aaa|b
aaaxb 0
xaaab 0
b 0
abbb 1 a|bbb
請注意,例如。aaa|b是.的結果join-了解每一組被捕獲的內容'|'..在這種情況下,組0(即模式匹配的內容)捕獲。aaa,第一組被俘b.
教課:你可以在一個環顧四周的地方捕捉到。你可以使用自由間距來提高可讀性.
步驟3:將前瞻性重構為“循環”
在我們引入我們的計數機制之前,我們需要對我們的模式做一次修改。當前,前瞻性不在+重復“循環”。到目前為止還好,因為我們只是想斷言b+跟隨我們a+但是我們真的最后要做的是為每個人斷言a我們在“循環”中匹配,有一個對應的b一起去吧。
現在我們不要擔心計數機制,只需按照以下步驟進行重構:
第一次重構a+到(?: a )+(請注意,(?:…)是一個非捕獲組)
然后在這個非捕獲組中移動前瞻。
請注意,我們現在必須“跳過”a*在我們“看到”之前b+,因此相應地修改模式。
因此,我們現在有以下幾點:
$r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
#? ? ? ? ? │? ? ?│? ? ? └──┘ │ │
#? ? ? ? ? │? ? ?│? ? ? ? 1? │ │
#? ? ? ? ? │? ? ?└───────────┘ │
#? ? ? ? ? │? ? ? ?lookahead? ?│
#? ? ? ? ? └───────────────────┘
#? ? ? ? ? ?non-capturing group
輸出與以前相同(如ideone.com所見),所以在這方面沒有任何變化。重要的是,現在我們的斷言是每一次迭代.的.+“循環”對于我們當前的模式,這是不必要的,但接下來我們將使用自引用使第1組“計數”。
教課:您可以在非捕獲組中捕獲。環顧四周是可以重復的。
第四步:這是我們開始計數的步驟。
下面是我們要做的事情:我們將重寫第一組,這樣:
的第一次迭代結束時,+,當第一次a是匹配的,它應該捕獲b
在第二次迭代結束時,當另一個a是匹配的,它應該捕獲bb
在第三次迭代結束時,它應該捕獲bbb
...
在n-第四次迭代,第一組應該捕獲bn
如果沒有足夠的b要捕獲到第1組,斷言就會失敗。
第一組,現在(b+),將不得不重寫為(\1 b)..也就是說,我們試圖“添加”一個b在上一次迭代中捕獲了哪些組1。
這里有一個小問題,因為這個模式缺少了“基本案例”,即它可以在沒有自引用的情況下進行匹配。因為組1啟動“未初始化”,所以需要一個基本的大小寫;它還沒有捕獲任何內容(甚至沒有一個空字符串),所以自引用嘗試總是失敗的。
有很多種方法可以解決這個問題,但是現在讓我們來做一個自引用匹配。任選,即.\1?..這可能是完美的,也可能不是完美的,但讓我們看看它能起什么作用,如果有什么問題,當我們到達它的時候,我們會穿過那座橋。同時,我們還將在測試用例中添加更多的測試用例。
$tests = array(
'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb'
);
$r4 = '/ ^ (?: a (?= a* (\1? b) ) )+ /x';
#? ? ? ? ? │? ? ?│? ? ? └─────┘ | │
#? ? ? ? ? │? ? ?│? ? ? ? ?1? ? | │
#? ? ? ? ? │? ? ?└──────────────┘ │
#? ? ? ? ? │? ? ? ? ?lookahead? ? │
#? ? ? ? ? └──────────────────────┘
#? ? ? ? ? ? ?non-capturing group
輸出現在是(如ideone.com所見):
aaa 0
aaab 1 aaa|b? ? ? ? # (*gasp!*)
aaaxb 0
xaaab 0
b 0
abbb 1 a|b? ? ? ? ? # yes!
aabb 1 aa|bb? ? ? ? # YES!!
aaabbbbb 1 aaa|bbb? # YESS!!!
aaaaabbb 1 aaaaa|bb # NOOOOOoooooo....
哈哈!看來我們現在離解決方案很近了!我們設法讓第一組使用自我引用“計數”!但是等等.。第二個和最后一個測試用例出了問題!沒有足夠的bS,不知怎么算錯了!我們將在下一步研究為什么會發生這種情況。
教課:一種“初始化”自引用組的方法是使自引用匹配可選。
步驟4.5:了解出了什么問題
問題是,由于我們使自引用匹配可選,“計數器”可以“重置”回0,當沒有足夠的。b讓我們仔細檢查一下在我們的模式的每一次迭代中發生了什么。aaaaabbb作為投入。
a a a a a b b b
↑
# Initial state: Group 1 is "uninitialized".
_
a a a a a b b b
↑
# 1st iteration: Group 1 couldn't match \1 since it was "uninitialized",
#? ? ? ? ? ? ? ? ? so it matched and captured just b
___
a a a a a b b b
↑
# 2nd iteration: Group 1 matched \1b and captured bb
_____
a a a a a b b b
↑
# 3rd iteration: Group 1 matched \1b and captured bbb
_
a a a a a b b b
↑
# 4th iteration: Group 1 could still match \1, but not \1b,
#? (!!!)? ? ? ? ? ?so it matched and captured just b
___
a a a a a b b b
↑
# 5th iteration: Group 1 matched \1b and captured bb
#
# No more a, + "loop" terminates
哈哈!在我們的第四次迭代中,我們仍然可以匹配\1但我們無法匹敵\1b!因為我們允許自引用匹配是可選的\1?,引擎回溯,并采取“不謝謝”選項,然后允許我們匹配和捕獲僅僅。b!
但是,請注意,除了在第一次迭代時,您始終可以只匹配自引用。\1..當然,這是顯而易見的,因為這就是我們在上一次迭代中捕獲的內容,而且在我們的設置中,我們總是可以再次匹配它(例如,如果我們捕獲了bbb上一次,我們得到保證bbb,但可能有也可能沒有bbbb這一次)。
教課*提防回溯。regex引擎將執行盡可能多的回溯,直到給定模式匹配為止。這可能會影響績效(即災難性回溯)和/或正確性。
第五步:自救!
現在,“修復”應該是顯而易見的:將可選的重復與占有性量詞也就是說,而不是簡單地?,使用?+相反(請記住,量化為占有式的重復不會倒退,即使這種“合作”可能會導致與總體模式的匹配)。
在非常非正式的條件下,這就是?+, ?和??說:
?+
(可選)“它不必在那里,”
(占有欲)“但如果它在那里,你必須接受它,而不是放手!”
?
(可選)“它不必在那里,”
(貪婪)“但如果是的話,你現在可以接受,”
(回溯)“但你以后可能會被要求放手!”
??
(可選)“它不必在那里,”
(不情愿)“即使是這樣,你也不必現在就接受它,”
(回溯)“但是你以后可能會被要求接受的!”
在我們的設計中,\1不會第一次出現,但它會總在那之后的任何時候,我們總想匹配一下。因此,\1?+就能實現我們想要的。
$r5 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ /x';
#? ? ? ? ? │? ? ?│? ? ? └──────┘ │ │
#? ? ? ? ? │? ? ?│? ? ? ? ? 1? ? │ │
#? ? ? ? ? │? ? ?└───────────────┘ │
#? ? ? ? ? │? ? ? ? ?lookahead? ? ?│
#? ? ? ? ? └───────────────────────┘
#? ? ? ? ? ? ?non-capturing group
現在輸出是:如ideone.com所見):
aaa 0
aaab 1 a|b? ? ? ? ? # Yay! Fixed!
aaaxb 0
xaaab 0
b 0
abbb 1 a|b
aabb 1 aa|bb
aaabbbbb 1 aaa|bbb
aaaaabbb 1 aaa|bbb? # Hurrahh!!!
哇!問題解決了!我們現在正確地計數,就像我們想要的那樣!
教課學習貪婪、不情愿和占有性重復之間的區別。可選的-占有可以是一個強大的組合。
第6步:最后修飾
所以我們現在得到的是一個匹配的模式a一而再、再而三地a是匹配的,有一個對應的b在第一組中捕獲。+在不再存在時終止a,或者如果斷言失敗是因為沒有對應的b為了a.
要完成這項工作,我們只需附加到我們的模式。\1 $..這現在是對第1組匹配的內容的反向引用,后面是行錨的末尾。錨確保沒有額外的b在字符串中,換句話說,實際上我們有anbn.
下面是最后確定的模式,還有其他測試用例,包括一個長度為10,000個字符的測試用例:
$tests = array(
'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb',
'', 'ab', 'abb', 'aab', 'aaaabb', 'aaabbb', 'bbbaaa', 'ababab', 'abc',
str_repeat('a', 5000).str_repeat('b', 5000)
);
$r6 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ \1 $ /x';
#? ? ? ? ? │? ? ?│? ? ? └──────┘ │ │
#? ? ? ? ? │? ? ?│? ? ? ? ? 1? ? │ │
#? ? ? ? ? │? ? ?└───────────────┘ │
#? ? ? ? ? │? ? ? ? ?lookahead? ? ?│
#? ? ? ? ? └───────────────────────┘
#? ? ? ? ? ? ?non-capturing group
它發現4個匹配:ab, aabb, aaabbb,以及a5000b5000..它需要只能在ideone.com上運行0.06s.
步驟7:Java測試
因此,該模式在PHP中工作,但最終目標是編寫一個在Java中工作的模式。
public static void main(String[] args) {
String aNbN = "(?x) (?:? a? (?= a* (\\1?+ b))? )+ \\1";
String[] tests = {
"",? ? ? // false
"ab",? ? // true
"abb",? ?// false
"aab",? ?// false
"aabb",? // true
"abab",? // false
"abc",? ?// false
repeat('a', 5000) + repeat('b', 4999), // false
repeat('a', 5000) + repeat('b', 5000), // true
repeat('a', 5000) + repeat('b', 5001), // false
};
for (String test : tests) {
System.out.printf("[%s]%n? %s%n%n", test, test.matches(aNbN));
}
}
static String repeat(char ch, int n) {
return new String(new char[n]).replace('\0', ch);
}
該模式如預期的那樣工作(如ideone.com所見).
現在我們得出結論.。
需要說的是a*在展望未來,確實是“主要的”+循環“,兩者都允許回溯。讀者被鼓勵確認為什么這在正確性方面不是一個問題,為什么同時使這兩個所有格都能起作用(盡管也許將強制性和非強制性的所有量詞混合在同一個模式中可能會導致誤解)。
還應該說,雖然它很整潔,但是有一個正則表達式可以匹配anbn這并不總是實踐中的“最佳”解決方案。一個更好的解決方案就是簡單的匹配^(a+)(b+)$,然后比較宿主編程語言中第1組和第2組捕獲的字符串的長度。
在PHP中,它可能如下所示(如ideone.com所示):
function is_anbn($s) {
return (preg_match('/^(a+)(b+)$/', $s, $groups)) &&
(strlen($groups[1]) == strlen($groups[2]));
}
本文的目的是不要讓讀者相信regex幾乎可以做任何事情;它顯然做不到,即使是它能做的事情,如果它帶來了一個更簡單的解決方案,至少應該考慮部分委托給宿主語言。
如上面所提到的,這篇文章必須加上標簽。[regex]對于堆棧溢出來說,它可能不僅僅是這樣。當然,學習斷言、嵌套引用、擁有量詞等是有價值的,但更重要的一課也許是一個人可以嘗試解決問題的創造性過程、當你受到各種約束時所需要的決心和辛勤工作,以及構建工作解決方案的各個部分的系統組合,等等。
獎勵材料!PCRE遞歸模式!
由于我們確實提到了PHP,需要指出的是,PCRE支持遞歸模式和子程序。因此,以下模式適用于preg_match (如ideone.com所見):
$rRecursive = '/ ^ (a (?1)? b) $ /x';
目前,Java的regex不支持遞歸模式。
更多的獎勵材料!匹配anbncn !!
所以我們已經看到了如何匹配anbn這是不定期的,但仍然是無上下文的,但我們也能匹配嗎?anbncn,哪一個甚至不是沒有上下文的?
答案當然是,是!鼓勵讀者自己解決這一問題,但解決方案如下(與在ideone.com上用Java實現).
總結
以上是生活随笔為你收集整理的java正则表达式 n_如何将a^nb^n与Java正则表达式匹配?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机图像图形设计制作 步骤,第三章
- 下一篇: 以jsp实现管理后台界面侧栏