目前 febird 中的自动机库已支持正则表达式,并且,支持的是多正则表达式匹配:
给定 M 个正则表达式,每个正则表达式有一个 [0, M) 的唯一 ID,该算法为这些正则表达式生成一个 DFA。
再给定一个输入文本 Text,长度为 T,假定只计最长匹配,该 Text 可以匹配 M 个正则表达式中的的 K 个。
在该DFA上运行我的匹配算法,可以在 O(T + K) 的时间复杂度内找到那 K 个正则表达式,这个时间复杂度与 M 完全没有关系!从信息论的角度讲,该算法是最优的。
如果要获得在 Text 中的所有 N 个匹配点(N<=T),所要的时间是 O(T + K1 + ... Kn)),其中 Ki 表示第 i 个匹配点能匹配的正则表达式数目。
这也许是现存的最高效的解决该问题的算法,之前我一直认为该问题很难解决:一个很难的字符串问题,现在,它解决了,世界平静了!
当初我提出该问题的时候,如果我那个时候就开始尝试解决它,也许永远解决不了!
该问题的最终解决,却是源于另外几个问题的解决,当我完美高效地实现DAWG之后,我就在想一个问题:如何实现一个动态的基于 DAWG 的 Map ?在 自动机的一些算法和应用 中,我想象了一个解决方案(类似红黑树索引号),但是后来经过仔细的思考,那个方案根本就行不通!
再后来,我又开始思考短语注音问题,短语注音问题的难点在于短语中的多音字,如果一个短语有多个多音字,这个短语所有可能的注音数量是指数的!如果我们只想从汉字短语得到它的所有可能注音,那这根本就不是问题!短语注音问题是:
给定一个汉字短语集合,以及每个汉字的所有发音(可能是多个,如果算上模糊音,就更多了)
要求:从该短语集合与汉字注音生成一个数据结构,该数据结构可以实现以下功能:
给定一串拼音,以最快的速度,找到这串拼音对应的短语。
这个问题一开始遇到的时候,觉得很困难,也就一直没有太上心,因为这是个Map问题,那个时候在我的大脑中,只有 DAWG 能实现基于 DFA 的 Map,而我知道 DAWG 根本无法解决这种指数问题。
再到后来,偶然一个机会,我也不记得具体的动机是什么,我为 DFA 增加了一个接口:
int match_key(char delim, string text, function<void(int keylen, string value)> on_match);
delim 一般是 \t ,创建用于该接口的 DFA 时,输入是一行行的 (key, value) 文本: key \t value
match_key 碰到 \t 时,将已匹配的字符串长度作为 keylen , \t 后面的部分作为 value,通过 on_match 回调返回给调用方,之所有用回调,是因为在 ADFA 中,同一个 key 对应多个 value 是一种很自然的事情,而这多个 value 可能非常多。总之,从用户来看,这是一个非常简单有效的接口。并且,自动机的创建是一个分离的通用的程序(adfa_build)。这就形成了一个最简单的生态:adfa_build 从文本文件生成 DFA 二进制文件,在线服务器程序加载 DFA 二进制文件并调用 match_key 。 不需要为每一个不同的服务器程序专门写一个 addfa_build !
然后,我很快就意识到,这可以解决注音问题,关键是生成那个 DFA!最简单的办法是从短语集合生成一个个 (pinyin, 汉字) 的 (key, value) pair,这里 key 是拼音,汉字是 value,如果一个短语有多音字,就生成多个 (key, value) pair,先不管对应同一个 value 的 key 数目可能因为多音字从而是指数个。只傻乎乎地将这些 (key, value) pair 输出给 adfa_build 程序,最终肯定能生成那个 DFA,而且是最小化的 DFA,因为该 DFA 创建过程是 Onfly Minimize 的,内存用量不会超爆。但是,但是——内存是不会爆,时间却仍然是指数级的!
这个问题曾经困扰了我很久,那段时间,我日思夜想,就是无法解决指数问题!后来终于有一天,洗澡的时候,我忽然意识到,用 NFA 作为媒介!因为我很早就实现了 Jan Daciuk 的 Onfly ADFA Minimization 算法,在 match_key 给我灵感之后,有一天突发奇想,结合 match_key 的 delim,对 Daciuk 的算法做了一个理论上的泛化: add_adfa_tail,这个 adfa_tail 可以拥有任何复杂的 DAG 结构,如果把那多个可能的注音放到这个 adfa_tail 中,问题就解决了一半,剩下的一半,就是 DFA 的翻转,很简单的事情!
………………
…………
……
再接下来,灵感继续光临,多正则表达式匹配,只需要在每个正则表达式之后,附加一个 delim+ID,一大半问题就解决了,剩下的一小半,就是纯粹的工程技术问题了
再后来,就是一些优化和泛化问题了,到目前为止,我的 DFA 多正则匹配算法还可以获取一类正则表达式的 submatch……