用"/(.*?)d/"正則表達式匹配"abcd"為什麼得到的是"abcd"而不是"d"?

而用"/a(.*?)/"正則表達式匹配"abcd"則只得到"a"

以及另一個問題:

用"/a(.*?)d/"正則表達式匹配"abcd"為什麼得到的是"abcd"而不是無法匹配?

PHP代碼:

關於"?"元字元

/*知乎首問*/


非貪婪是滿足條件即返回結果。

* 是零或任意多個,所以 a(.*?) 實際上是相當於:

a * = 0
a. * = 1
a.. * = 2
a... * = 3
… …

a(.*?) 去匹配 abcdddef ,相當於

abcdddef

a * = 0

* 0 的時候就匹配到了 a ,所以就返回 a

如果以 a(.*?)d 去匹配 abcdddef ,其實就相當於:

abcdddef
|
ad | * = 0
a.d| * = 1
a..d * = 2

所以返回 abcd


對於「 "/(.*?)d/"正則表達式匹配"abcd"為什麼得到的是"abcd"而不是"d"?」這個問題還在嚷嚷非貪婪匹配的我只能嘆一聲你們這些戰五渣。

首先你們說的正則匹配一般是指partial match,或叫search。js里應該就是exec,但是我沒查過。這個策略是獨立於正則表達式本身的。一旦用了這個策略,正則引擎將尋找起點最靠左的可行解。


因為無論你的正則表達式怎麼寫,第一個字元在元字元串的位置出現的越早,這個答案的優先順序就更高。


其實跟貪婪不貪婪沒什麼區別,原因只是因為正則是從前往後匹配而不是從後往前匹配


根本和貪婪非貪婪沒關係好么!!@Tim Shen 巨巨已經說了!!

有個很重要的前提是正則表達式得成功啊,就是說要找到一個可行解!

/(.*?)d/

你把d去掉試一試

/(.*?)/

這樣是直接就返回了

因為要找到一個解啊!!!所以當發現d的時候,就會儘力去匹配。

你們這群人啊。


因為正則是從前往後逐字元掃描的

a yes

b yes

c yes

d bingo


看到了一個非常棒的帖子,從NFA引擎機制上解釋了這一問題

引用過來供大家參考

本題相關關鍵內容已加粗並下劃線

(其中有兩張圖大家看完一定就明白了)

對於貪婪與非貪婪模式,可以從應用和原理兩個角度進行理解,但如果想真正掌握,還是要從匹配原理來理解的。

先從應用的角度,回答一下「什麼是貪婪與非貪婪模式?」

2.1 從應用角度分析貪婪與非貪婪模式

2.1.1 什麼是貪婪與非貪婪模式

先看一個例子

舉例:

源字元串:aa&test1&bb&test2&cc

正則表達式一:&.*&

匹配結果一:&test1&bb&test2&

正則表達式二:&.*?&

匹配結果二:&test1&(這裡指的是一次匹配結果,所以沒包括&test2&)

根據上面的例子,從匹配行為上分析一下,什是貪婪與非貪婪模式。

正則表達式一採用的是貪婪模式,在匹配到第一個「&」時已經可以使整個表達式匹配成功,但是由於採用的是貪婪模式,所以仍然要向右嘗試匹配,查看是否還有更長的可以成功匹配的子串,匹配到第二個「&」後,向右再沒有可以成功匹配的子串,匹配結束,匹配結果為「&test1&bb&test2&」。當然,實際的匹配過程並不是這樣的,後面的匹配原理會詳細介紹。

僅從應用角度分析,可以這樣認為,貪婪模式,就是在整個表達式匹配成功的前提下,儘可能多的匹配,也就是所謂的「貪婪」,通俗點講,就是看到想要的,有多少就撿多少,除非再也沒有想要的了。

正則表達式二採用的是非貪婪模式,在匹配到第一個「&」時使整個表達式匹配成功,由於採用的是非貪婪模式,所以結束匹配,不再向右嘗試,匹配結果為「&test1&」。

僅從應用角度分析,可以這樣認為,非貪婪模式,就是在整個表達式匹配成功的前提下,儘可能少的匹配,也就是所謂的「非貪婪」,通俗點講,就是找到一個想要的撿起來就行了,至於還有沒有沒撿的就不管了。

2.1.2 關於前提條件的說明

在上面從應用角度分析貪婪與非貪婪模式時,一直提到的一個前提條件就是「整個表達式匹配成功」,為什麼要強調這個前提,我們看下下面的例子。

正則表達式三:&.*&bb

匹配結果三:&test1&bb

修飾「.」的仍然是匹配優先量詞「*」,所以這裡還是貪婪模式,前面的「&.*&」仍然可以匹配到「&test1&bb&test2&」,但是由於後面的「bb」無法匹配成功,這時「&.*&」必須讓出已匹配的「bb&test2&」,以使整個表達式匹配成功。這時整個表達式匹配的結果為「&test1&bb」,「&.*&」匹配的內容為「&test1&」。可以看到,在「整個表達式匹配成功」的前提下,貪婪模式才真正的影響著子表達式的匹配行為,如果整個表達式匹配失敗,貪婪模式只會影響匹配過程,對匹配結果的影響無從談起。

非貪婪模式也存在同樣的問題,來看下面的例子。

正則表達式四:&.*?&cc

匹配結果四:&test1&bb&test2&cc

這裡採用的是非貪婪模式,前面的「&.*?&」仍然是匹配到「&test1&」為止,此時後面的「cc」無法匹配成功,要求「&.*?&」必須繼續向右嘗試匹配,直到匹配內容為「&test1&bb&test2&」時,後面的「cc」才能匹配成功,整個表達式匹配成功,匹配的內容為「&test1&bb&test2&cc」,其中「&.*?&」匹配的內容為「&test1&bb&test2&」。可以看到,在「整個表達式匹配成功」的前提下,非貪婪模式才真正的影響著子表達式的匹配行為,如果整個表達式匹配失敗,非貪婪模式無法影響子表達式的匹配行為。

2.1.3 貪婪還是非貪婪——應用的抉擇

通過應用角度的分析,已基本了解了貪婪與非貪婪模式的特性,那麼在實際應用中,究竟是選擇貪婪模式,還是非貪婪模式呢,這要根據需求來確定。

對於一些簡單的需求,比如源字元為「aa&test1&bb」,那麼取得div標籤,使用貪婪與非貪婪模式都可以取得想要的結果,使用哪一種或許關係不大。

但是就2.1.1中的例子來說,實際應用中,一般一次只需要取得一個配對出現的div標籤,也就是非貪婪模式匹配到的內容,貪婪模式所匹配到的內容通常並不是我們所需要的。

那為什麼還要有貪婪模式的存在呢,從應用角度很難給出滿意的解答了,這就需要從匹配原理的角度去分析貪婪與非貪婪模式。

2.2 從匹配原理角度分析貪婪與非貪婪模式

如果想真正了解什麼是貪婪模式,什麼是非貪婪模式,分別在什麼情況下使用,各自的效率如何,那就不能僅僅從應用角度分析,而要充分了解貪婪與非貪婪模式的匹配原理。

2.2.1 從基本匹配原理談起

NFA引擎基本匹配原理參考:正則基礎之——NFA引擎匹配原理。

這裡主要針對貪婪與非貪婪模式涉及到的匹配原理進行介紹。先看一下貪婪模式簡單的匹配過程。

源字元串:"Regex"

正則表達式:".*"

圖2-1

註:為了能夠看清晰匹配過程,上面的空隙留得較大,實際源字元串為「」Regex」」,下同。

來看一下匹配過程。

首先由第一個「"」取得控制權,匹配位置0位的「"」,匹配成功,控制權交給「.*」。

「.*」取得控制權後,由於「*」是匹配優先量詞,在可匹配可不匹配的情況下,優先嘗試匹配。從位置1處的「R」開始嘗試匹配,匹配成功,繼續向右匹配,匹配位置2處的「e」,匹配成功,繼續向右匹配,直到匹配到結尾的「」」,匹配成功,由於此時已匹配到字元串的結尾,所以「.*」結束匹配,將控制權交給正則表達式最後的「"」。

「"」取得控制權後,由於已經在字元串結束位置,匹配失敗,向前查找可供回溯的狀態,控制權交給「.*」,由「.*」讓出一個字元,也就是字元串結尾處的「」」,再把控制權交給正則表達式最後的「"」,由「"」匹配字元串結尾處的「"」,匹配成功。

此時整個正則表達式匹配成功,其中「.*」匹配的內容為「Regex」,匹配過程中進行了一次回溯。

接下來看一下非貪婪模式簡單的匹配過程。

源字元串:"Regex"

正則表達式:".*?"

圖2-2

看一下非貪婪模式的匹配過程。

首先由第一個「"」取得控制權,匹配位置0位的「"」,匹配成功,控制權交給「.*?」。

「.*?」取得控制權後,由於「*?」是忽略優先量詞,在可匹配可不匹配的情況下,優先嘗試不匹配,由於「*」等價於「{0,}」,所以在忽略優先的情況下,可以不匹配任何內容。從位置1處嘗試忽略匹配,也就是不匹配任何內容,將控制權交給正則表達式最後的「」」。

「"」取得控制權後,從位置1處嘗試匹配,由「"」匹配位置1處的「R」,匹配失敗,向前查找可供回溯的狀態,控制權交給「.*?」,由「.*?」吃進一個字元,匹配位置1處的「R」,再把控制權交給正則表達式最後的「"」。

「"」取得控制權後,從位置2處嘗試匹配,由「"」匹配位置1處的「e」,匹配失敗,向前查找可供回溯的狀態,重複以上過程,直到由「.*?」匹配到「x」為止,再把控制權交給正則表達式最後的「"」。

「"」取得控制權後,從位置6處嘗試匹配,由「"」匹配字元串最後的「"」,匹配成功。

此時整個正則表達式匹配成功,其中「.*?」匹配的內容為「Regex」,匹配過程中進行了五次回溯。

全文鏈接:

正則基礎之—

感謝原作者


第一個 a..d 對應過去就是abcd 兩個點分別匹配到bc

第二個 a 後面的(.*?)看都不用看

第三個 ...d 對應過去就是abcd 三個點分別匹配到abc


先到先得,

排隊——書上的傳統美德


這個正則寫的太菜


「.」是通配符,可以匹配任何字元。「 * 」的含義是出現0-n次

所以「.*」表示匹配任何一個字元到沒得匹配了或者這行結束

?跟在「 * 」後面表示非貪婪,就是說不會匹配掉「d」,d會留給正則裡面的「d」來匹配

所以「(.*?)d」表示匹配d和d前面的n個字元,符合這個模式的有:abcd,soshtnfihwbrd,ekahqbfkchwbrkd等,而不是僅僅是d。

如果你要匹配d,請使用「d」直接匹配。

如果你的意圖是提取子組d,php的語法我不知道,請自行查文檔找到類似於group()的方法來提取子組


看了這些回答似乎單獨說都不太對,總結一下:

貪婪模式和非貪婪模式 + 從左向右搜索


是貪婪模式和非貪婪模式的原因。


不理解的情況下,把非貪婪匹配時的問號? 去掉,按照默認的貪婪匹配去找,理解起來就明白多了。

/a(.*)/ 匹配abcd 能匹配到很多,非貪婪模式下,就最先返回a了

/(.*)d/ 匹配abcd 只能匹配到abcd,所以非貪婪模式下也只能返回abcd

/a(.*)d/ 匹配abcd 只能匹配abcd,所以非貪婪模式下也只能返回abcd


因為貪婪/非貪婪只是改變「各種不同匹配方式之間的優先(嘗試)次序」。

若只有一種匹配方式,那無論如何都會用那種方式匹配,區別只是中間嘗試失敗的次數不同。

事實上,正則引擎的基本原理是很單純的:

先從第1個字元開始嘗試,遇到多種匹配方式就選一種(這裡的選擇次序才是貪婪/非貪婪的分別),一種不行就換另一種,全部都不行就從第2個字元再開始,直到匹配成功,或者試完了所有可能為止。

(當然,這只是基本原理,中間可以有優化,例如有^之類的錨點的話,就不會試別的字元而直接回報匹配失敗)

所以,(.*?)d匹配abcd的過程是這樣的:

- 從字串開頭開始匹配

- .*?可以匹配任意多個字元,由於是非貪婪,先匹配0個試試

- 用d來匹配"abcd"中的"a",失敗

- 回滾到.*?,這次匹配1個("a")

- 用d來匹配"abcd"中的"b",失敗

- 回滾到.*?,這次匹配2個("ab")

- 用d來匹配"abcd"中的"c",失敗

- 回滾到.*?,這次匹配3個("abc")

- 用d來匹配"abcd"中的"d",成功

於是就匹配到了"abcd"。

假如字串變成了"xyz"的話,過程大概會是這樣:

- 從字串開頭開始匹配

- .*?可以匹配任意多個字元,由於是非貪婪,先匹配0個試試

- 用d來匹配"xyz"中的"x",失敗

- 回滾到.*?,這次匹配1個("x")

- 用d來匹配"xyz"中的"y",失敗

- 回滾到.*?,這次匹配2個("xy")

- 用d來匹配"xyz"中的"z",失敗

- 回滾到.*?,這次匹配3個("xyz")

- 用d來匹配時發現沒字元剩了,失敗

- 回滾到.*?,發現全部分支都試過了,失敗

- 回滾到選擇匹配位置的步驟,從字串跳過1個字元的位置開始匹配("yz")

- .*?可以匹配任意多個字元,由於是非貪婪,先匹配0個試試

- 用d來匹配"yz"中的"y",失敗

- 回滾到.*?,這次匹配1個("y")

- 用d來匹配"yz"中的"z",失敗

- 回滾到.*?,這次匹配2個("yz")

- 用d來匹配時發現沒字元剩了,失敗

- 回滾到.*?,發現全部分支都試過了,失敗

- 回滾到選擇匹配位置的步驟,從字串跳過2個字元的位置開始匹配("z")

- 從字串跳過2個字元的位置開始匹配

- .*?可以匹配任意多個字元,由於是非貪婪,先匹配0個試試

- 用d來匹配"z"中的"z",失敗

- 回滾到.*?,這次匹配1個("z")

- 用d來匹配時發現沒字元剩了,失敗

- 回滾到.*?,發現全部分支都試過了,失敗

- 回滾到選擇匹配位置的步驟,從字串跳過3個字元的位置開始匹配("")

- .*?可以匹配任意多個字元,由於是非貪婪,先匹配0個試試

- 用d來匹配時發現沒字元剩了,失敗

- 回滾到.*?,發現全部分支都試過了,失敗

- 回滾到選擇匹配位置的步驟,發現所有開始位置都試過了,回報匹配失報

當然,還是那句,這只是基本原理,中間很多步驟可以優化。

題主想深入理解的話,建議可以看《精通正則表達式》,挺不錯的,雖然我貌似已經忘了大半內容……

測試的話Online regex tester and debugger: JavaScript, Python, PHP, and PCRE很好用,有給出匹配時的解釋。


問題的關鍵應該在從左向右搜索上

(?=.*)d 可以達到你的要求,它搜索到了abc但是不佔有abc 只匹配d。

而 (.*?)d雖然是非貪婪的,但是搜索的過程中佔有了abc。

參考:正則基礎之—

左右的問題還有:

/(opera|netscape|gecko|msie)/

搜索

mozilla/5.0 (windows; U; Win98; en-US; rv:0.9.4) gecko/20011019 netscape6/6.2

匹配的結果是 gecko 優先 netscape


貪婪模式和非貪婪模式 前面很多人都說了


正則表達式多用才是王道!


因為非貪婪模式是符合匹配即返回

  • "/a(.*?)/"匹配abcd,返回"a"

這裡從"a"符合匹配,由於後面(.*?)是匹配0個或多個,這裡0個即符合匹配。所以返回"a"

  • "/a(.*?)d/"匹配"abcd",返回"abcd"

這裡從"a"開始符合匹配,到"d"結束。所以返回"abcd"

  • 對比貪婪模式"a(.*)d"匹配"abcdabcd",則會返回"abcdabcd"

推薦閱讀:

怎麼看待2017 年三月 Angular 直接跳 4?
你前端實習時得到的第一份任務是什麼?
2016年,中國的前端在關注什麼?
前端搞那麼多工具框架庫,是讓開發更簡單,還是更複雜?

TAG:前端開發 | JavaScript | PHP | 正則表達式 |