1959-正則表達式#
案件摘要#
1959 年,Stephen Kleene 的正則表達式理論完整版正式發表(1951 年 RAND 報告 → 1956 年論文 → 1959 年完整版):以三個運算 \(rs, r|s, r^*\) 從原子構築出「正則語言」,並證明它們恰好等價於有限自動機。一門從神經網路數學誕生的記法,最終成為全世界程式設計師的日常工具。
前因 -- 為什麼會有這個案子#
- 1943 年 McCulloch–Pitts 提出神經元的邏輯模型,用「事件串列」描述神經網路的行為。
- 1951 年 Kleene 在 RAND 報告《Representation of Events in Nerve Nets and Finite Automata》中,試圖刻劃「神經網路能辨識哪些事件」。
- Kleene 的偵探手法:發明一種代數記法(正則表達式)描述事件,再證明「正則表達式可描述的事件」=「有限自動機可辨識的事件」。這就是 Kleene 定理。
- 歷史線索的三段式:1951 報告首次提出 → 1956 論文(《Representation of Events in Nerve Nets and Finite Automata》, Automata Studies)嚴格證明 → 1959 完整版修訂定型。
線索與推理 -- 數學式、程式、理論#
正則表達式的文法#
在字母表 \(\Sigma\) 上,正則表達式(regex)遞迴定義:
\[ r ::= \; a \;(\forall a \in \Sigma) \;\mid\; \epsilon \;\mid\; \emptyset \;\mid\; r s \;\mid\; r \mid s \;\mid\; r^* \]
各構件的語言語義:
| 表達式 | 語義(語言) |
|---|---|
| \(a\) | \(\{a\}\) ,單一字元 |
| \(\epsilon\) | \(\{\epsilon\}\) ,空字串 |
| \(\emptyset\) | \(\varnothing\) ,空語言 |
| \(rs\) (串接) | \(\{xy : x \in L(r), y \in L(s)\}\) |
| \(r \mid s\) (聯集) | \(L(r) \cup L(s)\) |
| \(r^*\) (Kleene star) | \(\bigcup_{n \geq 0} L(r)^n = \{\epsilon, x_1x_2\ldots, \ldots\}\) |
範例: \((a \mid b)^* a (a \mid b)^*\) 描述「含至少一個 \(a\) 的字串」; \(a(ab)^*\) 描述「 \(a\) 後接零或多個 \(ab\) 」。
Thompson 構造(regex → NFA)#
Ken Thompson(1968)將 Kleene 的構造系統化成可實作的歸納法:每個 regex 對應一個 NFA,由小到大組合:
- \(a\) :兩狀態、一條 \(a\) -邊。
- \(rs\) :串接——將 \(r\) 的接受狀態以 \(\epsilon\) -邊連到 \(s\) 的起始狀態。
- \(r \mid s\) :新起始狀態以 \(\epsilon\) -邊分連 \(r, s\) 的起始。
- \(r^*\) :新接受狀態, \(r\) 的接受狀態以 \(\epsilon\) -邊回連起始(形成循環),並有 \(\epsilon\) -邊跳過。
\[ \text{size}(NFA) = O(|r|) \]
NFA 狀態數與 regex 長度成線性關係,這是 Thompson 的關鍵貢獻(相較於直接 determinization 的指數爆炸)。
正則表達式 = 有限自動機的雙向構造(Kleene 定理)#
Kleene 定理:語言 \(L\) 是正則的 \(\iff\) \(L\) 被某個有限自動機接受。
\[ \exists\, r \;[L = L(r)] \iff \exists\, M \;[L = L(M)], \; M \text{ 為 DFA} \]
雙向構造: - regex → NFA:Thompson 構造(上述),再由子集構造(subset construction)去隨機化成 DFA。 - NFA/DFA → regex:狀態消去法(state elimination,Brzozowski/McNaughton–Yamada 形式):對狀態 \(k\) 逐一消去,合併邊標籤:
\[ R_{ij}^{(k)} = R_{ij}^{(k-1)} \mid R_{ik}^{(k-1)} \left( R_{kk}^{(k-1)} \right)^* R_{kj}^{(k-1)} \]
最終從起始狀態到接受狀態的標籤即為 regex。代價:regex 可能指數膨脹( \(O(2^{O(n)})\) )。
Python re 模組與 Thompson NFA 模擬#
# --- Thompson NFA 構造與模擬(教學版) ---
class State:
def __init__(self):
self.eps = [] # epsilon 邊
self.trans = {} # 字元邊: char -> list[State]
def thompson(regex: str) -> State:
"""支援串接、|、* 的簡化遞迴下降構造(右結合解析)"""
pos = [0]
def parse_alt():
n1 = parse_cat()
if pos[0] < len(regex) and regex[pos[0]] == '|':
pos[0] += 1
n2 = parse_alt()
s, e = State(), State()
s.eps += [n1, n2] # 聯集:分連
e2 = State() # 匯合(簡化:共用)
return s
return n1
def parse_cat():
states = [parse_star()]
while pos[0] < len(regex) and regex[pos[0]] not in '|)':
states.append(parse_star())
for a, b in zip(states, states[1:]):
a.eps.append(b) # 串接:eps 邊連接
return states[0]
def parse_star():
n = parse_atom()
while pos[0] < len(regex) and regex[pos[0]] == '*':
s, e = State(), State()
s.eps += [n]
# r* : 跳過 + 循環
for st in _accepts_of(n): st.eps.append(s)
n = s
return n
def parse_atom():
c = regex[pos[0]]; pos[0] += 1
s = State(); s.trans[c] = [State()]
return s
def _accepts_of(n): return [n] # 簡化版:單一接受概念
return parse_alt()
def match(nfa: State, text: str) -> bool:
"""NFA 模擬:epsilon-closure + 字元轉移,O(len(text) * |NFA|)"""
current = {nfa}
def closure(states):
stack, seen = list(states), set(states)
while stack:
s = stack.pop()
for t in s.eps:
if t not in seen:
seen.add(t); stack.append(t)
return seen
current = closure(current)
for ch in text:
nxt = set()
for s in current:
if ch in s.trans:
nxt |= set(s.trans[ch])
current = closure(nxt)
if not current:
return False
# 簡化:檢查是否有狀態帶有「接受」標記(此教學版以最後狀態無出邊近似)
return any(not s.eps and not s.trans for s in current)
# --- 實務對照:Python re 模組 ---
import re
assert re.fullmatch(r"(a|b)*a(a|b)*", "bba") # 含至少一個 a
assert re.match(r"^\d{4}-\d{2}-\d{2}$", "2026-09-30") # 日期格式
assert re.findall(r"\b\w+\b", "hello regex world") == ["hello", "regex", "world"]
print("re 模組與 Thompson NFA:範例通過")
grep/regex 在實務程式設計的影響#
- 1968 年 Ken Thompson 在 UNIX 上實作
ed/grep,首次將 regex 帶入作業系統工具。 grep(Global Regular Expression Print)成為 UNIX 哲學的核心工具;regex 擴散到 awk、sed、Perl(Larry Wall 1987 大幅擴充)、PCRE、Pythonre、JavaScriptRegExp、Java/C# 等。- 應用場景:詞法分析器(lex/flex)、日誌解析、輸入驗證、搜尋替換、基因序列比對(正則近似)、網頁爬蟲。
- 效能戰場:Rust 的
regexcrate 與 RE2(Google, 2010)堅持線性時間 Thompson NFA 模擬,拒絕回溯(backtracking)以避免「災難性回溯」(catastrophic backtracking, \(O(2^n)\) )——正是 Kleene/Thompson 路線的現代復興。
結案 -- 後果與影響#
- 結案:Kleene 定理確立「正則表達式 = 有限自動機」,記法與機器合而為一。
- 理論影響:正則語言成為 Chomsky 階層 Type-3 層;泵引理、Myhill–Nerode 定理完善其理論;Brzozowski 導數(1964)提供另一條代數路線。
- 實務影響:regex 成為程式設計師的「第二母語」;從 UNIX 工具到現代語言標準庫無所不在。
- 深遠影響:regex 是「高階記法自動編譯成低階機器」的最早成功範例之一,預示了編譯器、DSL、甚至 LLM 時代的提示詞工程的「描述即程式」哲學。災難性回溯問題則提醒:理論上的線性時間演算法(Thompson)與實務實作的選擇同樣重要。
關鍵人物與文獻#
- Stephen Kleene(1909–1994):《Representation of Events in Nerve Nets and Finite Automata》, RAND Report RM-704, 1951;《Automata Studies》, 1956;完整版 1959。
- Warren McCulloch / Walter Pitts:1943 神經元模型,regex 的生物學源頭。
- Michael Rabin / Dana Scott:1956 年 NFA/DFA 等價性與有限自動機理論(Turing 獎 1976)。
- Ken Thompson:1968 年 UNIX
grep/QED 的 regex 實作;2007 年《Regular Expression Matching Can Be Simple and Fast》重申 Thompson NFA 路線。 - Janusz Brzozowski:1964 年正則表達式導數,regex→DFA 的代數方法。