这只是对 larsmans 已经写过的内容的解释。如果你喜欢这个答案,请另外给他投票。
有限自动机 FA 只是一组状态,并且规则说,如果您处于状态 S 并且输入的下一个字符是 c,那么您将转换到状态T。其中两个州是特殊的。一种表示“从这里开始”,另一种表示“我成功匹配”。其中一个字符很特殊,意思是“刚刚结束的字符串”。所以你拿一个字符串和一个有限自动机,从起始状态开始,不断地将字符输入机器并改变状态。如果您提供任何状态的意外输入,您将无法匹配。如果你达到“我成功匹配”的状态,你就成功匹配了。
现在有一种众所周知的算法可以将正则表达式转换为一个有限自动机,当且仅当该正则表达式匹配时,它才匹配字符串。 (如果您阅读过正则表达式,这就是 DFA 引擎的工作原理。)为了说明,我将使用模式 ^.*(44|3).*$,这意味着“字符串的开头,任意数量的字符,后跟 44 或 3,然后任意数量的字符,后跟字符串的结尾。"
首先让我们在寻找下一个字符时标记正则表达式中的所有位置:^A.*(4B4|3)C.*$
我们的正则表达式引擎的状态将是这些位置的子集,并且匹配的特殊状态。状态转换的结果将是我们在那个位置看到特定角色时可以达到的状态集。我们的起始位置在 RE 的开头,即 {A}。以下是可以达到的状态:
S1: {A} # start
S2: {A, B}
S3: {A, C}
S4: {A, B, C}
S5: matched
以下是过渡规则:
S1:
3: S3
4: S2
end of string: FAIL
any other char: S1
S2:
3: S3
4: S3
end of string: FAIL
any other char: S1
S3:
4: S4
end of string: S5 (match)
any other char: S3
S4:
end of string: S5 (match)
any other char: S4
现在,如果您使用任何字符串,请从状态 S1 开始,并遵循规则,您将匹配该模式。这个过程可能漫长而乏味,但幸运的是可以自动化。我的猜测是 larsmans 已将其自动化以供自己使用。 (技术说明,从“RE 中的位置”到“您可能处于的位置集”的扩展可以像这里一样预先完成,也可以在运行时完成。对于大多数 RE,最好预先完成, 就像这里一样。但是一小部分病态的例子会以非常多的状态结束,最好在运行时做这些。)
我们可以使用任何正则表达式来做到这一点。例如^([1-9]|1[0-9]|2[0-7])$ 可以被标记为:^A([1-9]|1B[0-9]|2C[0-7])D$ 我们得到状态:
T1: {A}
T2: {D}
T3: {B, D}
T4: {C, D}
和过渡:
T1:
1: T3
2: T4
3-9: T2
any other char: FAIL
T2:
end of string: MATCH
any other char: FAIL
T3:
0-9: T2
end of string: MATCH
any other char: FAIL
T4:
0-7: T2
end of string: MATCH
any other char: FAIL
好的,所以我们知道什么是正则表达式,什么是有限自动机,以及它们之间的关系。两个有限自动机的交集是什么?它只是一个有限自动机,当两个有限自动机单独匹配时匹配,否则不匹配。它很容易构造,它的状态集只是一个状态和另一个状态的对的集合。它的转换规则是只对每个成员独立应用转换规则,如果其中一个失败,则整体执行,如果两者都匹配,则两者都执行。
对于上述对,让我们实际执行数字13 上的交集。我们从状态(S1, T1)开始
state: (S1, T1) next char: 1
state: (S1, T3) next char: 3
state: (S3, T2) next char: end of string
state: (matched, matched) -> matched
然后在号码上14。
state: (S1, T1) next char: 1
state: (S1, T3) next char: 4
state: (S2, T2) next char: end of string
state: (FAIL, matched) -> FAIL
现在我们来到了这个重点。给定最终的有限自动机,我们可以使用动态规划来计算有多少字符串匹配它。这是计算:
0 chars:
(S1, T1): 1
-> (S1, T3): 1 # 1
-> (S1, T4): 1 # 2
-> (S3, T2): 1 # 3
-> (S2, T2): 1 # 4
-> (S1, T2): 5 # 5-9
1 chars:
(S1: T2): 5 # dead end
(S1, T3): 1
-> (S1, T2): 8 # 0-2, 5-9
-> (S2, T2): 1 # 3
-> (S3, T2): 1 # 4
(S1, T4): 1
-> (S1, T2): 6 # 0-2, 5-7
-> (S2, T2): 1 # 3
-> (S3, T2): 1 # 4
(S2, T2): 1 # dead end
(S3, T2): 1
-> match: 1 # end of string
2 chars:
(S1, T2): 14 # dead end
(S2, T2): 2 # dead end
(S3, T2): 2
-> match 2 # end of string
match: 1
-> match 1 # carry through the count
3 chars:
match: 3
好的,工作量很大,但我们发现有 3 个字符串同时匹配这两个规则。我们以一种可自动化且可扩展到更大数量的方式来做到这一点。
当然,我们最初提出的问题是有多少匹配第二个而不是第一个。好吧,我们知道 27 匹配第二条规则,3 匹配两者,所以 24 必须匹配第二条规则而不是第一条。
正如我之前所说,这只是 larsmans 解决方案的阐述。如果您学到了一些东西,请为他投票,为他的答案投票。如果这些材料听起来很有趣,那就去买一本Progamming Language Pragmatics 之类的书,了解更多关于有限自动机、解析、编译等的知识。这是一个非常好的技能组合,而太多的程序员没有。