关于Earley第二篇论文给的建立parse tree的算法的bug

今天检查自己的算法,发现有个以前写的注释问是不是有错,印象里这个问题已经解决了啊,原来Tomita的论文还不知道放哪儿去了,妈的。查了一下,看见这个:

> I pulled the following message from the website text search. I find it
> hard to follow the example in the message though, and wonder if anyone
> can further explain why Earley's parsing extension to his recognizer
> algorithm given in his Ph.D. thesis does not correctly build parse
> trees. Any examples or references to papers would be appreciated. Thanks.

Elizabeth Scott presented a paper on the subject at LDTA'07: _SPPF-Style
Parsing From Earley Recognisers_, to appear in ENTCS. An example of
incorrect Earley parsing given there (and originally from Tomita) is the
grammar

        S -> S S | a

with input "aaa", for which Earley's parsing procedure allows spurious
derivations for "aaaa" and "aa".

过了一遍我的算法,觉得不可能这样,试了一下果然没有。隐隐约约记得Earley的算法中在Item上做链接记录哪些Item导致了当前的Item,而上面的例子中每个Item都有可能是两个情况导致的,但这些情况没有被区分。

(正确的推导:(aa.a,a.aa),错误的推导:(a.a,aa.aa)。)

好像类似问题在我使用Earley算法前自己完全自创算法时也碰到过,所以就避开了。该死的注释,也不写清楚点。真没心情去回忆到底怎么个来龙去脉了,而且对我这个版本的正确性还是比较有信心的。

我的方法是State(c)上做链接,而链接到c上的a和b又是两个State。(我的State是一个对Earley Item的引用还是就是类似Earley Item的东西?早知道应该和那些论文统一用词...),不同的是,我的State记录了导致它的State的position。

对于最终的a)S-> S S有b)S->(S S) S和c)S->S (S S),导致a最后一个S的完成的两个State,显然分别记录了导致它们的State的position,b是2,c是1,这样就不会产生混淆的情况。

问题:关于复杂度。对于Earley的这个错误,论文里的解决方法印象里有两种:1)每次复制一个新的Item以区别的是无法保证O(n^c)的,2)用做了标记的pointer在Item之间互相指的是最坏n^3的。我的方法和这两种都不一样,复杂度有没有受影响呢?

这两者的区别在本质上说,1生成了全部的推导树,在执行过程中每次都创建新的Item,下一次链接这个新的Item,等于记住了所有排列组合;2生成的是DAG,或者说重用了节点的推导树。

比如对于a)S->(S S S) S,有b)S->((S S) S) S和c)S-> (S (S S)) S,但把a变成d)S->S S的时候,我们并不关注d中第一个S是符合b的情况还是c的;而最后得到不同路径的推导树,有正确的DAG就足够了(Earley的是错的DAG)

(update:终于翻出了Earley 1970的论文,粗看了一遍初步认为跟我的实现基本一样,现在怀疑是其它书籍/论文作者给理解/弄错了。)

我没有仔细分析我的算法和解决方法2有什么不同,粗略的想,pointer上的标记实际上也是记录了和我类似的信息,而且初步判断不会比我记录的更少。也就是说,我们记录的信息都是充分必要的,所以按理说,我的算法应该也具有同样的复杂度。

不过,有时间还是仔细计算一遍为好。(其它:更多的,考虑我上次未完成的想法,那些找茬语法的出现根本上在于信息表达有误,在这个例子里,递归不等于迭代,表达方式本身可能就是一种失误)

在这做个笔记,省得万一真有问题,也算个线索,别忘了。下次记得同步注释.....,尤其是这种事情,完全不写注释也避不开,因为毕竟用了别人的算法,需要重新掌握的时候碰见各种各样的信息可能就会糊涂。

顺便骂两句很多写论文的,总是喜欢不交代清楚就贬低别人的东西。比如很多所谓的“论文”就是号称Tomita或者XX多么快,弄一些貌似专业的测试,至于细节什么都没讲。

事实上呢,Earley如果经过一些调整,只比LR(1)慢1.9倍(Tomita的parser就是一个LR的扩展),但却可以解析全部上下文无关文法,而不仅仅是子集,扩展性也强。

(给感兴趣的提供些背景知识:大家都知道LR是O(n)的,但事实上LR不能覆盖全部的CFG;而基本所有能处理全部CFG的都是最坏n^3的,更直观的说,大多数平时用到的语法这些parser执行起来都是O(n))

说白了,相比LR流的算法,Earley原始版本的算法问题有二:1. 没有很好的优化。2. 没有预先计算。这俩问题已经有人做了一些工作(比如Aycock和Horspool),上面的调整就指的他们的成果。

其次,很多LR扩展出来的generic parser和上下文相关的parser,也开始包含类似于Earley的想法,比如Tomita那个;一大票本来就是和Earley一脉相承的算法更不用提了。

我为什么喜欢Earley算法?

真看懂这个算法以后,就会发现相比任何达到相同功能的算法,没有比Earley算法在思路上更加简洁优美的模型了。基于抽象结果的属性,它还是对优化、扩展相对开放的,完全可以视为一个“算法框架”。

跟这个算法相比,LR给人的感觉就是冷冰冰的;而LR的各种扩展算法如果不说丑,也会让刚接触的人难受。究其原因,是因为Earley一开始就选对了抽象的方式,而Knuth却有点直奔主题。

简洁、通用的东西,改进和扩展相对容易;而一个精心设计的产物,再去动它就不那么痛快了。

我相信那些基于LR扩展、而不是基于Earley改进的研究者,很大程度上是在他们初学时受到潜移默化的干扰、有太多先入为主的观念才做出在他们自己看起来“自然而然”的选择。

所以我当初再三权衡之下,还是下决心排除干扰,选了Earley算法。

在我看来Earley跟同时代的人相比,虽然综合水平一定比不上Knuth这样的大师,但是他绝对是一个拥有直觉的聪明人。可惜,此人后来好像投身心理学了。计算机就这么没劲?

posted on 2011-02-06 01:54  怪怪  阅读(1769)  评论(17编辑  收藏  举报

导航