爲何要寫這麼一篇文章呢?是由於本身最近在研究和學習正則表達式,而後在RegexGolf上練習技能的時候遇到了這麼一道題目,以爲頗有趣。我當時雖然也解決了這個問題,可是正則表達式寫的有點長,並且也只算是一種取巧的解決方案。由於若是測試用例再多一點可能我寫的這個正則表達式就不可以知足需求了。javascript
後來在覆盤這道題目的解決方案的時候,查閱了不少相關的資料。發現了更簡潔,更準確的答案。當我看到答案的那一瞬間,我突然發現本身當初距離這個答案其實也不遠,若是本身當時再好好研究一下,有可能就想出了更簡潔的答案了。固然,也許最終仍是沒有想出來,那裏有那麼多的若是呢😂。java
說了這麼多,讓咱們來看一下這道題目吧。以下圖所示:git
咱們須要匹配左邊的這些字符串,同時排除右邊的這些字符串。不知道你們以前有沒有作過相似的題目,如今你們就能夠測試一下本身的正則的水平。我的感受若是這道題目能夠作出來的話,那麼你的正則水平至少是中等偏上的水平啦。沒有作出來也沒有關係,畢竟在平時的開發過程當中咱們很難會遇到這種需求,不過也能夠學習一下。加深一下本身對正則匹配過程的理解。github
我當時看了題目的提示,知道是匹配素數,可是對於如何匹配一個素數我倒是不知道的。因而我就取了個巧,在測試用例不是不少的狀況下,能夠進行窮舉呀。因而我就寫下了下面的答案:正則表達式
^(?!(?:(x{2}){2,}|(x{3}){2,}|(x{5}){2,})$)
雖然有點長,可是好歹也算是經過了測試用例。app
對正則表達式學習的比較深刻的一些同窗,看到了上面的正則應該很快就知道是什麼意思了。我能夠先簡單的給你們講解一下。上面的正則表達式匹配的過程是這樣的:函數
^
:匹配一行的開始。(?!...)
:否認的順序環視,匹配一個位置,後面緊跟着的是須要排除的條件。(?:)
:非捕獲型括號,在這裏用來限制多選結構的做用範圍,固然在這裏也可使用捕獲型的括號。(x{2}){2,}
:首先{2,}
限定了前面的表達式(x{2})
出現的次數只能是兩次或者兩次以上。(x{2})
表示須要匹配兩個x
。(x{3}){2,}
和(x{5}){2,}
:這兩個部分跟上面的正則片斷是相似的意思。$
:匹配一行的結尾。因此上面的正則表達式表示的意思就是匹配一個字符串的開始,而後緊接着字符串中x的個數不能是2個x的2倍或者以上,不能是3個x的2倍或者以上,不能是5個x的2倍或者以上;直到字符串的結尾。學習
接下來,咱們針對這個問題深刻的研究一下。首先咱們須要知道什麼是素數,素數就是除了1和它自己以外不可以被其餘整數整除的數,不包括1。那麼咱們怎麼判斷一個數字是否是素數呢?咱們假設這個數字是N,而後咱們用N去除以2,看一下是否可以整除,不可以整除的話,咱們再用N去除以3,若是還不能夠就除以4,一直除到N/2(N/2若是不可以整除,須要取整)。若是都不能夠的話,咱們就能夠斷定N是一個素數。測試
若是咱們能夠把上面這個過程使用正則表達式表示出來,那麼咱們就可以匹配一個素數了。可是想要一會兒解決這個問題可能有點難,咱們須要按照剛纔的思路一步一步來構造咱們的正則表達式。首先咱們能夠轉換一下思路,直接匹配一個素數有點難度,咱們能夠先匹配一個合數,而後排除掉匹配的這個合數就能夠了。網站
首先咱們須要匹配可以被2整除的數,按照上面題目的要求的話,咱們須要匹配一個字符串,這個字符串中x
出現的次數須要是偶數次。對於這個問題,咱們很容易想到這樣一個表達式/(xx)+/
;若是須要匹配可以被3整除的數的話,按照上面題目的要求,咱們很容易想到正則表達式/(xxx)+/
,因而到這裏你可能會以爲我好像知道了匹配可以被大於等於2整除的數的答案,那就是/(xx+)+/
。你能夠本身試一下這個正則表達式,結果並非你所想的那樣。
爲何呢?由於正則表達式/(xx+)+/
的意思是,(xx+)
能夠出現至少一次,可是(xx+)
多是2個x
,也多是3個x
或者4個x
。上面的正則表達式不可以知足在同一次的匹配過程當中(xx+)
都表示相同個數的x
。那麼咱們應該如何解決這個問題呢?這個時候咱們就要思考,在正則表達式中什麼能夠表示已經匹配了的內容呢?沒錯,就是捕獲與反向引用。若是咱們須要在匹配的過程當中繼續匹配相同的次數的話,咱們須要使用反向引用表示前面已經匹配的結果。那麼到這裏,這個正則表達式就呼之欲出了。這個正則表達式就是:
/(xx+)\1/
我來解釋一下上面的這個正則表達式,首先(xx+)
表示x
的個數>=2,\1
表示反向引用,引用的值就是(xx+)
已經匹配的值。當(xx+)
後面緊跟着\1
時,(xx+)
在實際中到底能匹配多少個x
呢?這由要匹配的字符中出現多少個x
決定的。咱們一塊兒來看一下上面的正則表達式能匹配的x
的個數。
上面的圖二中,藍色背景表示整個正則表達式匹配的內容。在圖三中,青色部分代表了(xx+)
匹配的內容。\1
匹配的內容和(xx+)
匹配的內容是同樣的。這就是捕獲((xx+)
)與反向引用(\1
)。
上面的正則表達式表示了字符串的長度可以被二等分,咱們還要更進一步;咱們須要字符串的長度還能夠被三等分,四等分甚至更多。那咱們應該怎麼作呢?答案彷佛已經很明顯了,咱們只須要在\1
後面添加一個+
就能夠了。
/(xx+)\1+/
這樣,上面的正則表達式表示的意思就是,首先匹配2個或2個以上的x
,而後記住匹配的結果。接下來須要將這個匹配結果重複至少1次以上。在這裏須要注意的是+
是貪婪匹配,它會把它做用的那個匹配匹配儘量多的次數。
因此到目前爲止咱們已經能夠成功的匹配字符串的長度是合數的這些字符串了,可是還不知足題目的需求。咱們要作的是須要排除上面匹配到的這些結果。遇到這種狀況,咱們首先想到的應該就是順序環視了,固然咱們在這裏須要使用否認的順序環視(?!...)
,它表示當前位置的後面不可以出現匹配...
的狀況。
因此這個正則表達式的完整結果就是:
/^(?!(xx+)\1+$)/
其中^
和$
也是必須的,對於這裏的^
的意思是,匹配字符串的開頭,而後接下來整個(?!(xx+)\1+$)
匹配的也是一個位置,它表示在這個位置的後面字符串中x
的長度不可以是一個合數,直到字符串的結尾。
到這裏爲止,關於上面題目的講解就算完成啦。可是咱們尚未解決如何匹配一個真正的素數而不是一個字符串的問題。固然有了上面上面的經驗,我想這對你們來講應該也不是一個難題了。咱們能夠把這個素數N,轉換成一個字符串,這個字符串中的全部字符都是相同的,而且長度爲N。那麼咱們立刻就能夠寫出一個這樣的函數。在這裏咱們使用JavaScript語言來表示這樣一個函數。以下所示:
const isPrime = (n) => /^(?!(11+)\1+$)/.test(new Array(n).fill(1).join('')); console.log(isPrime(2)); // true console.log(isPrime(3)); // true console.log(isPrime(4)); // false console.log(isPrime(5)); // true
在這裏給你們推薦幾個學習和練習正則表達式的網站:
關於正則表達式匹配一個素數的原理到這裏就結束啦,若是你們有什麼疑問和建議均可以在這裏提出來。歡迎你們關注個人公衆號「關山不難越」,咱們一塊兒學習更多有用的正則知識,一塊兒進步。