400行Python實現一個正則++引擎!有興趣?看看我的EasyRegex吧

地址: GitHub - JimChengLin/EasyRegex: Python Regex Engine for Humans

100%覆蓋率測試通過.

在介紹使用方法之前, 先說下, 我認為目前Regex具有的問題

1. 目標和邏輯不分

比如, 匹配10個"a". 標準寫法: "a{10}". 識別的目標是"a", 邏輯是重複10次. 但Regex引擎為了統一API, 要求輸入是string. 這樣就帶來了各種需要""轉義的尷尬. 另外, 不那麼智能的IDE將沒有能力檢測其中的語法錯誤.

處於同樣境地的還有SQL, 但SQL有大量的優秀ORM. 不知為何, 封裝Regex的程序, 給我的感覺都很crappy. 我們需要一個真正意義上可維護的正則引擎. 這就是我的目標之一. 有個經典笑話, "當一個程序員決定用正則解決一個問題的時候, 那他就有了兩個問題".

2. 不支持XOR.

OR和NOT自帶支持. AND等價於向前和向後環視, 但鮮有支持XOR的. 我的EasyRegex補全了這一遺憾. 儘管這個實際需求可能不是很高.

3. 不能識別HTML標籤

這是所有標準正則語言本身的局限, 其只能描述有限個狀態. 但我希望能通過一些最小化且符合直覺的擴展, 讓正則有能力匹配嵌套的HTML標籤.

使用方法

from R import r# 匹配abcm = r(abc)m.match(abcdabdabccc)# >> [Result(0, 3, {}), Result(7, 10, {})]# Result(0, 3, {}) 表示從位置0匹配到位置3, 捕獲組為空# 用 @ 連接 R 對象, 以匹配更長的字元串m = r(abc) @ r(d) @ r(a) # 等價於 r(abcda)m.match(abcdabdabccc)# >> [Result(0, 5, {})]# 函數作為匹配目標m = r(1) @ r(str.isalpha) @ r(1)m.match(a1a1)# >> [Result(1, 4, {})]# 帶數量條件的匹配m = r(b, {1,2}) @ r(cd)m = r(b, (1, 2)) @ r(cd) # 二者等價m.match(bbcda)# >> [Result(0, 4, {})]from R import Mode# 懶惰模式(默認貪婪模式)m = r(ab) @ r(c, *, Mode.lazy)m = r(ab) @ r(c, (0, inf), Mode.lazy) # 二者等價m.match(abcccc)# >> [Result(0, 2, {})]# 通配符dot = r(lambda char: True)m = r(a) @ dot.clone(*) @ r(a)m = r(a) @ r(lambda char: True, *) @ r(a) # 二者等價m.match(123a123a123)# >> [Result(3, 8, {})]# 嵌套m = r(r(a), 5)m = r(a, 5) # 二者等價m.match(qaaaaaq)# >> [Result(1, 6, {})]m = r(q) @ r(r(a), +, Mode.lazy)m = r(q) @ r(a, (1, inf), Mode.lazy) # 二者等價m.match(qaaaaaa)# >> [Result(0, 2, {})]# ANDm = (r(abc) & r(abc)) @ r(d)m.match(abcd)# >> [Result(0, 4, {})]startswith_abc = r(abc) @ dot.clone(*)endswith_abc = dot.clone(*) @ r(abc)m = startswith_abc & endswith_abc # 等價於標準正則的 abc.*abcm.match(1abchhabc1)# >> [Result(1, 9, {})]# ORm = (r(a) | r(b)) @ r(bc)m.match(abcbbc)# >> [Result(0, 3, {}), Result(3, 6, {})]# NOTdigit = r(str.isdigit)no_digit = ~digit # 非數字m = no_digit.clone(+)m.match(123yyyyy123)# >> [Result(3, 8, {})]# XORm = (r(ab) ^ r(ab)) @ r(c)m.match(abc)# >> []# 帶捕獲組的匹配m = r(b, {1,2}, :b) @ r(cd)m.match(bbcda)# >> [Result(0, 4, {:b: [(0, 1), (1, 2)]})]# 捕獲組影響數量條件m = r(b, +, :b) @ r(cd, :b)# 含義: 貪婪匹配一或多個b並以:b為名字存入捕獲組# 接下來需要cd的個數是名為:b的捕獲組的長度m.match(bbcdcd)# >> [Result(0, 6, {:b: [(0, 1), (1, 2)]})]# 函數數量條件m = r(a, name=:a) @ r(b, lambda capture: len(capture.get(:a,())) + 1)# 含義: 匹配1個a並存入捕獲組, 接下來b的個數是名為:a的捕獲組的長度加一# 為了省略篇幅, 大家可以看下各個方法的簽名, 我注釋寫得很認真# 更多更詳細的例子, 參見 git 中的 test.py 文件

最後, 讓我們匹配一個嵌套的DIV標籤吧

div_head = r(<div, name=:head)div_tail = r(</div>, name=:tail)no_head_tail = ~(div_head | div_tail)# 當捕獲組內:head和:tail的長度相等時返回0, 否則為1def stop_head_tail_equal(capture: dict): head_group = capture.get(:head, ()) tail_group = capture.get(:tail, ()) return 1 if not head_group or not tail_group or len(head_group) != len(tail_group) else 0# 是一個不可能存在的字元, 這裡拿來當開關用sentinel = r(, stop_head_tail_equal)div = div_head @ r(div_head | div_tail | no_head_tail, +) @ div_tail @ sentinel# 含義: 不斷匹配 DIV 頭標籤和尾標籤, 直到二者數量相同m.match(0<div>1<div>2</div>3</div>4)# >> [Result(1, 26, {:head: [(1, 5), (5, 11)], :tail: [(13, 19), # >> (19, 26)]})]# 0<div>1<div>2</div>3</div>4[1:26] == <div>1<div>2</div>3</div>

推薦閱讀:

最全常見演算法工程師面試題目整理(二)
未來是屬於掌握演算法的公司
c++自學演算法導論 利用遞歸二分法 快速排序 請問哪裡寫v錯了?
刷完了leetcode,接下來刷哪個網站比較好呢?

TAG:算法 | 编译原理 | Python |