不到40行程式碼構建正則表示式引擎
譯者注:如何用不到40行的程式碼構建一個正則表示式引擎?作者在本文就將他本人的解決思路記錄了下來,如果你也想挑戰,不妨借鑑一下作者的思路,說不定你寫的程式碼可能不到30行。以下為譯文。
無意之間我發現了一篇文章,Rob Pike用C語言實現了一個正則表示式引擎的模型。於是我也嘗試用Javascript寫一個,並且增加了測試規範。測試規範和解決方案都放在了GitHub倉庫上面。本文將重點介紹解決方案。
問題描述
正則表示式引擎將支援以下語法:
最終目標是用最少的程式碼提供最強大的功能,從而滿足上述正則表示式用例。
單字元匹配
第一步是編寫一個函式,該函式有兩個入參,返回值是一個布林型別,表示匹配結果。.
簡單舉一些用例:
matchOne('a', 'a')
-> true
matchOne('.', 'z')
-> true
matchOne('', 'h')
-> true
matchOne('a', 'b')
-> false
matchOne('p', '')
-> false
function matchOne(pattern, text) { if (!pattern) return true // Any text matches an empty pattern if (!text) return false // If the pattern is defined but the text is empty, there cannot be a match if (pattern === ".") return true // Any inputted text matches the wildcard return pattern === text }
相同長度的字串匹配
現在需要增加引數的長度,並且暫時只考慮pattern和string長度相同的情況。根據以前的經驗,我很自然的認為這種情況非常適合用遞迴來解決。 我們只需要反覆呼叫matchOne
函式就可以了。
function match(pattern, text) { if (pattern === "") return true // Our base case - if the pattern is empty, any inputted text is a match else return matchOne(pattern[0], text[0]) && match(pattern.slice(1), text.slice(1)) }
上面的程式碼首先將pattern[0]
與text[0]
進行比較,然後將pattern[1]
與text[1]
進行比較,並繼續將pattern[i]
與text[i]
進行比較,直到i === pattern.length - 1
。如果在某個地方沒有匹配成功,那麼最終返回的結果就是匹配失敗。
我們來舉個例子。假設呼叫match('a.c','abc')
,實際上返回的就是matchOne('a','a')&& match('.c','bc')
。
如果繼續分析下去,其實最終的結果就是matchOne('a','a')&& matchOne('.','b')&& matchOne('c','c')&& match("","")
,這就相當於true && true && true && true
,所以返回結果就是true!
$字元
接下來增加特殊字元$
的支援,它可以匹配字串後面的所有字元。要想實現該功能,只需要在上一步的match
函式中增加一個額外基本情況的判斷就可以了。
function match(pattern, text) {
if (pattern === "") return true
if (pattern === "$" && text === "") return true
else return matchOne(pattern[0], text[0]) && match(pattern.slice(1), text.slice(1))
}
^字元
讓我們新增對特殊模式字元^
的支援,它允許匹配字串的開頭。這裡我將介紹一個新的函式–search
。
function search(pattern, text) {
if (pattern[0] === "^") {
return match(pattern.slice(1), text)
}
}
這個函式將成為程式碼的新入口。到目前為止只是在文字開始時才開始匹配。現在只是通過強迫使用者以^
來開始。但是如何支援文字中出現的任何模式呢?
任意位置的匹配
截止到目前為止,下面的表示式將會返回true
。
search("^abc", "abc")
search("^abcd", "abcd")
但是search("bc", "abcd")
返回的卻是undefined
。我們期望讓它返回true
。
如果使用者沒有指明要從第一個字元開始就要匹配,那麼我們希望在文字內的每個可能的起始點進行搜尋。這是預設的處理規則,除非pattern是以^
開始。
function search(pattern, text) {
if (pattern[0] === "^") {
return match(pattern.slice(1), text)
} else {
// This code will run match(pattern, text.slice(index)) on every index of the text.
// This means that we test the pattern against every starting point of the text.
return text.split("").some((_, index) => {
return match(pattern, text.slice(index))
})
}
}
?字元
使用?
的話,那麼在?
前面的0個或者1個字元可以進行匹配。
這裡有一些範例:
search("ab?c", "ac")
-> true
search("ab?c", "abc")
-> true
search("a?b?c?", "abc")
-> true
search("a?b?c?", "")
-> true
第一步是修改match
函式,當檢測到?
字元出現以後就開始呼叫matchQuestion
函式,matchQuestion
函式的定義將會在下面的內容看到。
function match(pattern, text) {
if (pattern === "") {
return true
} else if (pattern === "$" && text === "") {
return true
// Notice that we are looking at pattern[1] instead of pattern[0].
// pattern[0] is the character to match 0 or 1 of.
} else if (pattern[1] === "?") {
return matchQuestion(pattern, text)
} else {
return matchOne(pattern[0], text[0]) && match(pattern.slice(1), text.slice(1))
}
}
matchQuestion
函式需要處理兩種情況:
?
之前的字元沒有匹配成功,但是?
後面所有的文字都和pattern的剩餘部分匹配成功了;?
之前的字元都匹配成功了,並且其餘的文字(這裡應該減去一個字元)也和pattern的剩餘部分匹配成功了;
上面兩種情況中只要滿足一種,那麼matchQuestion
函式就會返回true
。
讓我們先考慮第一種情況。如果pattern中的文字除了_?
不一樣以外,其它都能匹配成功,這種情況我們怎麼去檢查呢?換句話說,如果?
前面的字元只出現了0次,這種情況我們怎麼去檢查呢?我們從pattern剔除掉2個字元(第一個字元就是?
前面的哪個,第二個字元就是?
本身),然後再呼叫match函式。
function matchQuestion(pattern, text) {
return match(pattern.slice(2), text);
}
第二種情況更具挑戰性,但是和上面介紹的一樣,它還是重用了之前已經寫好的函式。
function matchQuestion(pattern, text) {
if (matchOne(pattern[0], text[0]) && match(pattern.slice(2), text.slice(1))) {
return true;
} else {
return match(pattern.slice(2), text);
}
}
如果text[0]
跟pattern[0]
匹配上了,而且其它的文字和pattern中剩餘的也能匹配上,那麼我們就成功了。注意,程式碼我們也可以這麼寫:
function matchQuestion(pattern, text) {
return (matchOne(pattern[0], text[0]) && match(pattern.slice(2), text.slice(1))) || match(pattern.slice(2), text);
}
我更喜歡後面一個方法的原因是因為它明確地指出了有兩種情況,只要滿足其中一種,那麼返回的結果就是true
。
*字元
我們希望能夠匹配*
前面0個或多個字元。
下面這些表示式的返回結果都應該是true
。
search("a*", "")
search("a*", "aaaaaaa")
search("a*b", "aaaaaaab")
這個跟?
的情況很相似,我們在match
函式裡面再增加一個matchStar
方法。
function match(pattern, text) {
if (pattern === "") {
return true
} else if (pattern === "$" && text === "") {
return true
} else if (pattern[1] === "?") {
return matchQuestion(pattern, text)
} else if (pattern[1] === "*") {
return matchStar(pattern, text)
} else {
return matchOne(pattern[0], text[0]) && match(pattern.slice(1), text.slice(1))
}
}
matchStar
和matchQuestion
一樣,也要處理兩種情況:
*
前面的部分沒有匹配成功,但是其它文字和pattern中*
後面的都匹配成功了;*
前面的部分匹配成功了,並且其它文字和pattern中*
後面的也都匹配成功了;
由於這兩種情況都能決定匹配的結果,因此我們知道matchStar
可以用布林型別OR來實現。此外,matchStar
的情況1與matchQuestion
的情況1完全相同,因此同樣也可以使用match(pattern.slice(2),text)
進行實現。這意味著我們只需要制定滿足情況2的表示式。
function matchStar(pattern, text) {
return (matchOne(pattern[0], text[0]) && match(pattern, text.slice(1))) || match(pattern.slice(2), text);
}
重構
現在我們可以回過頭來,對search
函式進行簡化,而且正好可以將我從Peter Norvig寫的類裡面學到的一個技巧應用上。
function search(pattern, text) {
if (pattern[0] === "^") {
return match(pattern.slice(1), text)
} else {
return match(".*" + pattern, text)
}
}
我們使用*
字元本身來允許pattern中字串可以出現在任何地方。前面的.*
表示在pattern前面出現了任何數量的任何字元,我們也希望能匹配成功。
結論
功能如此強大,但是程式碼卻如此簡潔明瞭,這真是一件很了不起的事情。完整的原始碼可以再GitHub倉庫中找到。
相關推薦
不到40行程式碼構建正則表示式引擎
譯者注:如何用不到40行的程式碼構建一個正則表示式引擎?作者在本文就將他本人的解決思路記錄了下來,如果你也想挑戰,不妨借鑑一下作者的思路,說不定你寫的程式碼可能不到30行。以下為譯文。 無意之間我發現了一篇文章,Rob Pike用C語言實現了一個正則表示式引擎的模型。於是我
1000行程式碼徒手寫正則表示式引擎【1】--JAVA中正則表示式的使用
簡介: 本文是系列部落格的第一篇,主要講解和分析正則表示式規則以及JAVA中原生正則表示式引擎的使用。在後續的文章中會涉及基於NFA的正則表示式引擎內部的工作原理,並在此基礎上用1000行左右的JAVA程式碼,實現一個支援常用功能的正則表示式引擎。它支援貪婪匹配和懶惰匹配;支援零寬度字元(如“\b”, “\B
驗證URL連結和IP有效性的JS程式碼(正則表示式)
#js驗證一個URl字串是否有效 function isValidURL(url){ var urlRegExp=/^((https|http|ftp|rtsp|mms)?:\/\/)+[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+
正則表示式引擎的構建——基於編譯原理DFA(龍書第三章)——2 構造抽象語法樹
簡要介紹 構造抽象語法樹是構造基於DFA的正則表示式引擎的第一步。目前在我實現的這個正則表示式的雛形中,正則表示式的運算子有3種,表示選擇的|運算子,表示星號運算的*運算子,表示連線的運算子cat(在實際正則表示式中被省去)。 例如對於正則表示式a*b|c,在a*
C#後臺程式碼使用正則表示式判斷是否符合要求
C#後臺使用正則表示式: string pattern = @"^[0-9a-zA-Z_]{1,10}$";//字母數字下劃線,1到10位 bool result = false; if (!string.IsNullO
批量快速修改程式碼的正則表示式替換
[\W]*?X 跨行匹配任意字元到X字元結束 $1 為匹配到的(xxx)變數 " /** * XXXX */ private" 替換為: " @ApiModelPropertyvalue = "XXXX" p
Linux正則表示式引擎(BRE ERE)支援的一些表達形式(Part.I BRE)
BRE(basic regular expression):以sed為例 純文字 :echo "Happy New Year" | sed -n '/Happy/p' 錨字元 : 匹配在行首 :echo "Happy New Year" | sed -n '/^
正則表示式引擎測試筆記
判斷是否是傳統型NFA 用nfa|nfa not 來匹配 “nfa not” 字串 1. 如果只有 nfa 匹配 ,則是傳統型nfa。 2. 如果整個nfa not 都能匹配,則要麼是POSIX NFA ,要麼是 DFA。 是DFA還是POSIX NFA
re2正則表示式引擎學習(五)
改寫為DFA匹配時的執行過程。 首先打印出來的是NFA的結構,然後將NFA的結構轉化為DFA的結構,構建對應的DFA轉移矩陣。然後根據轉移矩陣進行匹配 執行時,正則表示式為ab*c|d,匹配的字串為d ab*c|d 9. alt -> 6 | 8 6. alt -&
實現一個正則表示式引擎in Python(一)
前言 專案地址:Regex in Python 開學摸魚了幾個禮拜,最近幾天用Python造了一個正則表示式引擎的輪子,在這裡記錄分享一下。 實現目標 實現了所有基本語法 st = 'AS342abcdefg234aaaaabccccczczxczcasdzxc' pattern = '
實現一個正則表示式引擎in Python(二)
專案地址:Regex in Python 在看一下之前正則的語法的 BNF 正規化 group ::= ("(" expr ")")* expr ::= factor_conn ("|" factor_conn)* factor_conn ::= f
實現一個正則表示式引擎in Python(三)
專案地址:Regex in Python 前兩篇已經完成的寫了一個基於NFA的正則表示式引擎了,下面要做的就是更近一步,把NFA轉換為DFA,並對DFA最小化 DFA的定義 對於NFA轉換為DFA的演算法,主要就是將NFA中可以狀態節點進行合併,進而讓狀態節點對於一個輸入字元都有唯一的一個跳轉節點 所以對於D
不到1000行的正則表示式程式碼分析07
不到1000行的正則表示式程式碼分析07 早晨先翻開ruby0.49下的regex.c,發現還是頭大,因為太長了,而且邏輯太複雜,比oz的複雜了不止一個數量級。於是仍舊回到oz的grep.c下的正則引擎原始碼。 昨天在睡覺時,一直在想,grep.c的正則引警是NFA,因為匹配時是正則表示式作主導,而
python正則表示式大作業之模擬計算器(29行程式碼)
今天很開心,完成了一項艱鉅的作業,剛開始見到這個作業時我是有些懵逼的,一心想著用findall精準匹配,但是發現匹配後無法處理資料,後來看了點兒老師的思路——用search一個一個地匹配然後替換,然後
正則表示式(十五)——統計程式碼中的程式碼行、註釋行和空白行
package com.wy.regular; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFo
MyEclipse去除網上覆制下來的程式碼帶有的行號(使用正則表示式)
一、正則表示式去除程式碼行號 作為開發人員,我們經常從網上覆制一些程式碼,有些時候複製的程式碼前面是帶有行號,如: MyEclipse本身自帶有查詢替換功能,並且支援正則表示式替換,使用正則替換就可以很容易去除這些行號 使用快捷鍵“ctrl+F”開啟MyEclipse的查詢替換功能,如
這20個正則表示式,讓你少寫1,000行程式碼
正則表示式——古老而又強大的文字處理工具。僅用一段簡短的表示式語句,就能快速地實現一個複雜的業務邏輯。掌握正則表示式,讓你的開發效率有一個質的飛躍。 正則表示式經常被用於欄位或任意字串的校驗,比如下面這段校驗基本日期格式的JavaScript程式碼:
VS2013 用正則表示式統計程式碼行數
公司 軟體申請 著作權 這鬼東西,所以在網上找了下統計方法,網上的正則表示式 (^:b*[^:b#/]+.*$)似乎不行,查了原因 去掉了了 : 號的匹配可行,具體如下: ^b*[^:b#/]+.*$
不到1000行的正則表示式原始碼分析06
不到1000行的正則表示式原始碼分析06 今天想談NFA與DFA的區別,對程式碼來說。 我喜歡購書,只要是經典的書,就買了。但當時也許看不懂。記得,《設計模式》一出來時,就購買了。可總是看不懂。直到看了程傑寫的《大話設計模式》才基本看懂。 其實,學正則表示式,有本經典書《精通正則表示式》也是經典,買