如何使用字符范围实现正则表达式 NFA?
Posted
技术标签:
【中文标题】如何使用字符范围实现正则表达式 NFA?【英文标题】:How to implement regular expression NFA with character ranges? 【发布时间】:2014-01-13 01:25:48 【问题描述】:当您阅读诸如Regex: NFA and Thompson's algorithm 之类的帖子时,一切看起来都相当简单,直到您意识到在现实生活中您不仅需要像“7”或“b”这样的直接字符,而且还需要:
[A-Z]
[^_]
.
即字符类(或范围)。因此我的问题是——如何使用字符范围构建 NFA?使用诸如“不是 A”、“其他任何东西”之类的元字符,然后计算重叠范围?这将导致在使用最终自动机时使用树状结构,而不仅仅是一个表格。
更新:请假设大小不小 (>>256) 字母。
我问的是 NFA,但后来我也想将 NFA 转换为 DFA。
【问题讨论】:
您能澄清一下“使用字符范围构建 NFA”的意思吗 @revo,用<a,k>
标记边缘,这意味着如果输入是j
,则使用此标签,但如果输入是z
,则不使用此标签。这并不难,但是有几个重叠的标签(<a,k>
、h
、<,>
)可能会导致混乱。而且我不喜欢重新发明***,因此我在问。
关键在于你如何表现边缘。对于 8 位字符集,考虑 256 位的位图。例如,如果设置了位 n,则字符代码 n 在允许集中。
@tripleee,谢谢。我认为 8 位在实践中不会再飞了,Unicode 更有可能。但这意味着每个标签 8 KB (!)。这种方法是在任何公开的程序中使用的,还是只是理论上的想法?
如果您确实需要支持 Unicode,则需要考虑一系列其他注意事项。请参阅UTS-18 了解正则表达式支持。与此同时,也许只考虑以一种明智的方式支持 UTF-8 作为开始。
【参考方案1】:
最简单的方法是:
在 NFA 和 DFA 中使用段作为转换的标签。例如,范围 [a-z] 将被表示为段 [97, 122]
;单个字符“a”将变为[97,97]
;和任何字符“。”会变成[minCode, maxCode]
。
每个取反范围 [^a-z] 将导致从开始状态到下一个状态的两次转换。在此示例中,应创建两个转换 [minCode, 96]
和 [123, maxCode]
。
当范围通过枚举所有可能的字符 [abcz] 来表示时,应该创建每个字符的转换,或者代码可能首先将字符分组到范围中以优化转换的数量。所以 [abcz] 会变成[a-c]|z
。因此两个转换而不是四个。
这对于 NFA 来说应该足够了。但是,当字符范围相交时,将 NFA 转换为 DFA 的经典 power set construction 将不起作用。 要解决这个问题,只需要一个额外的泛化步骤。一旦创建了一组所有输入符号,在我们的例子中它将是一组线段,它应该被转换为一组不相交的线段。这可以在 O(n*Log(n)) 时间内完成,其中 n 是使用优先级队列 (PQ) 的集合中的段数,其中段按左组件排序。示例:
Procedure DISJOIN:
Input <- [97, 99] [97, 100] [98, 108]
Output -> [97, 97] [98, 99], [100, 100], [101, 108]
第 2 步。要从“设置状态”创建新的转换,应修改算法如下:
for each symbol in DISJOIN(input symbols)
S <- empty set of symbols
T <- empty "set state"
for each state in "set state"
for each transition in state.transitions
I <- intersection(symbol, transition.label)
if (I is not empty)
Add I to the set S
Add transition.To to the T
for each segement from DISJOIN(S)
Create transition from "set state" to T
为了在搜索转换和输入符号 C 时加快匹配速度,每个状态的转换可以按段排序并应用二分搜索。
【讨论】:
以上是关于如何使用字符范围实现正则表达式 NFA?的主要内容,如果未能解决你的问题,请参考以下文章