6

我正在尝试进行正则表达式匹配。我已经写出了所有功能,但它们没有按应有的方式工作。据我所知,当我尝试比较列表时出现问题。
例如,“re_contains(a,a)”。给出 true (显然),就像 "re_contains(union(a,b),a)" 一样。

但只要我把它列在清单上,它就失败了。“重新包含(序列(a,b),[a,b])。” 返回假。Append 应该通过所有可能的组合来找到匹配项,但是这些函数都不能正常工作。这让我觉得也许我错过了一个基本案例。但我认为“re_contains(X, L) :- X == L。” 应该照顾它。我必须在这里寻找一些重要的东西。

这是我的代码:

re_contains(empty, []).

re_contains(X, L) :-
    X == L.

re_contains(seq(X, Y), L) :-
    append(L1, L2, L),
    re_contains(X, L1),
    re_contains(Y, L2). 

re_contains(union(X, _), L) :-
    re_contains(X, L).

re_contains(union(_, Y), L) :- 
    re_contains(Y, L).  

re_contains(kleene(X), L) :- 
    append([Car|L1], L2, L),
    re_contains(X, [Car|L1]),
    re_contains(kleene(X), L2).

re_contains(kleene(_),[]).
4

2 回答 2

8

有几个问题。以下是最明显的:

打字。 您的谓词re_contains/2需要一个列表作为第二个参数。成功是re_contains(a,a).巧合而非意图。

单调性。另一个问题是re_contains([a],[a])成功了,但re_contains([X],[a])失败了。或者,换个角度看:re_contains([X],[a])失败,但X = a, re_contains([X],[a])成功。通过添加目标X = a,我们专门化了查询,因此最初失败的查询应该再次失败。

身份测试 ( ==/2) 应替换为相等 ( =/2)确保我们有一些列表。所以:

re_contains(X, L) :-
    % X == L。
    X = L,
    附加(X,_,_)。

请注意,append/3此处仅用于确保 X 是一个列表 - 不使用实际的附加功能。

效率。第三个问题涉及您如何表示串联的实际方式。让我们看看以下规则:

re_contains(seq(X, Y), L) :-
    附加(L1,L2,L),
    re_contains(X, L1),
    重新包含(Y,L2)。

现在,假设我们这里有一个长度列表N。目标可能有多少个答案append(L1, L2, L)?其实N + 1。而这一点,不管所涉及的实际值如何。现在考虑:

?- 长度(L,1000000), 时间(re_contains(seq([a],[]),[b|L]))。
% 2,000,005 次推理,0.886 CPU 在 0.890 秒内(100% CPU,2258604 唇)
错误的。

re_contains/2这里需要的时间与列表的长度成正比。但是只要看看第一个元素就足以意识到这是不可能的。

所以背后的问题是使用append/3. Prolog 有一个简单的经验法则:如果您使用过多,请append/3考虑使用 s — 定从句语法。请查看标签以获取更多详细信息 - 并查阅介绍性 Prolog 文本。为了让您开始,这里是您定义的一个子集:

re_contains(RE, L) :-
    短语(重新(RE),L)。

重新([])-> []。
重新([E])-> [E]。
重新(序列(X,Y))-->
    重新(X),
    重新(Y)。

不再调查整个列表:

?-长度(L,1000000),时间(短语(re(seq([a],[])),[b|L]))。
% 6 次推理,0.000 秒内 0.000 CPU(88% CPU,127313 唇)
错误的。

顺便说一句,是一个完整的定义。

终止/不终止。与效率相关的是终止属性。理想情况下,如果可以有限地表示一组解决方案,则查询将终止。也就是说,通过有限数量的答案。好的,这就是我们正在努力的理想。Prolog 的简单但非常有效的执行算法有时会循环,当可能有有限数量的答案时。理解不终止的原因有时非常棘手。通常的调试策略——比如使用调试器进行跟踪或单步调试——不起作用,因为它们向您展示了太多细节。幸运的是,有一种更好的技术:

我不会再看你的整个程序,而只会看它的一小部分。该片段是一个故障切片(有关更多详细信息,请参见链接)。它小得多,但讲述了很多关于原始程序的信息——前提是它是一个纯粹的、单调的程序:

如果失败切片没有终止,则原始程序不会终止。

因此,如果我们找到这样的失败片段,我们可以立即对整个程序做出结论。甚至没有阅读其余部分!

这是一个有趣的失败片段:

...
 re_contains(X, L) :- false,
     X = L
re_contains(seq(X, Y), L) :-
    附加(L1,L2,L),假,
    re_contains(X,L1)re_contains(Y,L2)re_contains(union(X, _), L) :- false,
     re_contains(X, L)。
...

现在考虑目标re_contains(seq([],[]),L).理想情况下,应该只有一个答案L = []并且目标应该终止。但是,它会循环,因为append(L1, L2, L)不会终止。将此与上面终止的 DCG 解决方案进行对比phrase(re(seq([],[])),L)

于 2012-09-25T07:54:24.820 回答
5

append/3将拆分L,两者都L1L2是列表。

我会尝试re_contains(X, L) :- X == L.re_contains(X, [X]).

更改后,re_contains(a,a).将失败。

您以不同的方式表示序列,而您的匹配器并没有同时提供这两种方式。实际上,“工作”的唯一情况不是序列。

于 2012-09-25T07:28:44.297 回答