今天又想起了这个问题,感觉有必要记录一下。一方面,这个曾经花费我半年多的时间,而且我当时也号称,流行的Regex引擎应该扫入历史垃圾堆了,但最终,至少我没真正达成这个目标;另一方面,这个事情实在是太耗费时间了,眼前还是腾不出时间来继续深入。
所以最好的办法应该是先回顾一下老的东西、把思路整理一下,万一未来再有机会攻坚,好歹不至于什么都忘了。
首先,这个问题是一个纯正的NP问题。这意味着,它也是一个研究可计算性问题的模型,至于这个模型是否比其它模型更容易着手...,因为我没有真正研究过其它NP问题,所以也不好说。既然是一个NP问题,这个问题的研究就有两各方面:
1. 既然是NP问题,如果为这个问题找一个多项式时间的解法,就等于证明了NP==P;或者,如果能做出一个证明,证明这个问题不可能有一个多项式时间的解法,那么就等于证明了NP!=P。也许还存在一种可能性,即证明这个问题不可能知道它是NP还是P,那么我们就得到这个NP是不是等于P是不可证的结论。
任何一个人在这类问题上得到突破,那就成了超过Knuth的大师之中的大师了,所以这方面作为兴趣爱好没事琢磨一下也就算了,完全不能存有不切实际的幻想。
2. 实用性的,为这个问题找一个合理的解法,即把“当前流行的Regex引擎扫入历史的垃圾堆”。这个,就我的试验来说,还是非常可能的。什么叫做合理的解法呢?我的版本是,这个算法在(输入,时间)对儿上是自适应的。极端的说,只要一个(模式,输入)对儿存在在O(x)的解法,那么这个通用算法在这个输入上就应该也是O(x),x在这里代表一个数量级。
当前的Regex引擎最大的问题就是,为了支持反向引用等功能,在明显可以线性时间解完的、甚至不存在反向引用的输入上,也落入指数时间里去了。我去年暂停时,得到了一个自己的算法,但是这个算法在一些输入上存在比较严重的BUG,另一方面,它也太复杂了,难于继续优化。
目前我在其它地方的实践了Earley-Style的Parser,感觉也可以用于RegEx。为什么选择Earley的路子呢,因为他的算法和我得到的那个非常复杂的算法有一样的味道。虽然原装的Earley算法只能支持到非确定性下推自动机,但是凭着我以前的思路,可以轻易的扩展它,从而支持上下文相关语言;而且整体框架上似乎比我自己的算法更加清晰。
现在的想法是,能否在Predict或者Complete阶段,动态生成产生式,对应我以前算法中动态生成自动机的功能。有一点是非常需要注意的,即我原来生成自动机时,实际上是保留了相当的信息叫做“线索”。显然的,即便转移到Earley算法上,这部分也是必不可少,那么Earley已经提供的有哪些,没提供的是否很容易添加进去呢。需要注意的是我原来的算法遇到的难题是不是更好解决呢?
最后记录一下以前研究过程中碰到的难点。实际上反向引用并不是类似问题的真正障碍。障碍是反向引用和别的元素结合而产生的。比如(abc)de\1,这个RegEx是可以非常容易的被解析的,因为它虽然带有反向引用,但本质上和abcdeabc毫无二致。
但是,类似于(.*)(.*)\1之类的模式一下子就复杂得多,这很(最)大程度上是因为解析到一半时因为信息不足而存在的歧义性。如果在每个输入字符上都存在歧义(然后每种可能性在下一个字符上又再次出现歧义),可想而知最后的排列组合是多少。(在这里需要重 点记录的是一个清晰的认识:歧义数量是由模式和 串共同决定的)
(UPDATE:原来还有一个想法是,对模式预先处理,比如合并同类项等等,这个想法还是有比较多的道理的。这里有一个关键,即必须识别不能、或者说处理了也没用的模式,一些模式不如丢给回溯算法就行了)
过去的一个思路是把情况分类,然后分别优化,(UPDATE:这个是说的太误导了)针对(RegEx的)特殊功能扩展算法、把一些模式和情况认为是设计的特性加以处理(而不是认为它们仅仅是语法糖展开(比如数数功能)),我觉得这个思路还是可以采取的。
因为这种方式和机械的“有反向引用则A,没有则B”(可恶的是,大多数流行引擎连这个都没有)相比,可以更多的优化(这应该不是优化了)描述并处理带有反向引用但可以在多项式甚至线性时间内解决的情况。不过在此之前,还是应该仔细分析这个方案的付出与收获。在过去的实践中,这些特性与传统自动机观点形成的算法之间的配合,有一些复杂。那么它是不是能很好的和Earley算法框架结合呢?
另外,如果是根本在某一时刻就存在歧义(而不是因为操作不当产生的),肯定是无法在得到进一步信息前消除的。所以关键是如何处理好这些歧义,让(待定、但非人为的)歧义和算法时间之间有一个比较好的函数关系;如果能建立起这样一个模型,那么实际上这个实用性目标就至少可以说真正走出了第一步。
由于我过去太过希望我的这个解决方案是可以应用于流、而且可以抛弃读过的内容,所以关于进一步信息、和历史信息(可能还有一些其它的决策上)的处理上,太过局限在有限的手法上。最终事实是,至少对于历史信息,对于复杂的RegEx,我们无论如何处理,也要有相应的存储及索引方案,这样实际上并不能得到预期的限制在有限的那几种方法时可以获得的好处。
包括对当前输入的处理,实际上我们无需在每个position上都必须马上反应,而是可以根据一个动态决策的引擎来决定是不是反应,如何反应。这样的好处显而易见,得到的信息越完全,处理起来就越容易,这就代表更快的速度和更少的空间消耗。如果采用这种方式,实际上Earley算法作为框架也可能是不合适的,因为它基本上也是输入驱动型的。
(UPDATE:这个夜里想了想,似乎没有太好的动态决策的办法,但是也许预处理提取一定的信息是不错的方法。)
写到这里比我记这个笔记时所想到的内容要多了,最后这段想法是缺乏仔细斟酌的,还需要深化。另一方面,在这个研究过程中,我始终有意地避免使用启发式算法。因为总感觉match是严格的,而启发式算法不能保证取到最符合规则的那个结果。不过事实上,现有的RegEx引擎基本就没有完全符合规范的,我是不是太那个了?
(UPDATE:关键是启发式算法,或者说启发式算法的思路,是不是有很好的用武之地)
无论如何,这个问题不能放弃,要作为长期的一个研究爱好下去。可能有朋友会问,为什么这个问题会如此的吸引你呢,是不是你这人就不能承认失败、学不会放弃呢。
也许有这个因素,但并不主要。关键在于,这个问题属于又基本又难的那种、和人类思考本质挂钩、或者说有相似性的问题。这种问题的研究和最终我采取的解法,也会反过来影响我看待世界的方式。就是这样。