List &引用;“堆栈外”;简单Prolog谓词错误
我一直在练习序言。我尝试编写的函数是compose(L1、L2、L3)。它由L1和L2元素按顺序交错组成,直到其中一个元素变为nil,然后在末尾附加非nil列表。当输入L1和L2时(即打印出正确的L3),函数工作得非常好,但当输入L3并尝试获取所有逻辑上可能的输入L1和L2时,我遇到了“堆栈外”错误。例如,对于以下功能代码List &引用;“堆栈外”;简单Prolog谓词错误,list,prolog,failure-slice,List,Prolog,Failure Slice,我一直在练习序言。我尝试编写的函数是compose(L1、L2、L3)。它由L1和L2元素按顺序交错组成,直到其中一个元素变为nil,然后在末尾附加非nil列表。当输入L1和L2时(即打印出正确的L3),函数工作得非常好,但当输入L3并尝试获取所有逻辑上可能的输入L1和L2时,我遇到了“堆栈外”错误。例如,对于以下功能代码 compose([],[],[]). compose(L1,[],L3):- append(L1,[],L3). compose([],L2,L3):- ap
compose([],[],[]).
compose(L1,[],L3):-
append(L1,[],L3).
compose([],L2,L3):-
append([],L2,L3).
compose([H1|T1],[H2|T2],L3):-
compose(T1,T2,Tail),
append([H1],[H2],Head),
append(Head,Tail,L3).
?-compose(L1,L2,[a,b,c]).
将给我一个堆栈外错误。我应该如何解决这个问题?一个“全局堆栈外”错误通常意味着您的递归陷入无限循环中,因此一直调用谓词,直到堆栈耗尽
此处进入infinte循环的原因是,它将首先发出结果:
?- compose(L1,L2,[a,b,c]).
L1 = [a, b, c],
L2 = [] ;
L1 = [],
L2 = [a, b, c] ;
L1 = [a, c],
L2 = [b] ;
但随后它将寻找更多的解决方案。它根据您的程序,通过每次将第一项与[H1 | T1]
统一,第二项与[H2 | T2]
统一来实现这一点。然后立即进行递归调用,因此Prolog无法检查H1
和H2
是否实际上是第三个参数中的前两个元素
然而,我们首先不需要所有这些append/3
调用等。我们可以对参数进行简单的统一:
compose([],L2,L2).
compose(L1,[],L1).
compose([H1|T1],[H2|T2],[H1, H2|T3]) :-
compose(T1,T2,T3).
对于给定查询,该查询将在建议的解决方案出现后终止:
?- compose(L1,L2,[a,b,c]).
L1 = [],
L2 = [a, b, c] ;
L1 = [a, b, c],
L2 = [] ;
L1 = [a],
L2 = [b, c] ;
L1 = [a, c],
L2 = [b] ;
false.
实际上,由于递归,第三个参数最终将是一个空列表,因此无法与[H1,H2 | T3]
列表统一
我应该如何解决这个问题
首先,试着理解为什么查询没有终止。您可以尝试想象Prolog是如何进行的,但请注意,这可能会变得相当复杂。毕竟,Prolog结合了两个控制流(AND-AND或控制),并且它有一些在更传统的语言(OO和FP)中不存在的部分未知数据。因此,我不喜欢模仿Prolog,而是让Prolog帮助我定位错误。为此,我在您的程序中添加了尽可能多的目标false
,这样查询仍然不会终止。以下是最大值,称为a:
组合([]、[]、[]):-false。
组合(L1,[],L3):-false,
附加(L1、[]和L3)。
组合([],L2,L3):-false,
追加([],L2,L3)。
组成([H1 | T1]、[H2 | T2]、L3):-
合成(T1,T2,尾部),假,
附加([H1]、[H2],头部),
附加(头部、尾部、L3)。
-组成(L1,L2,[a,b,c]),假。
我们可以跳过你的第一个条款。只有最后一条规则的第一个目标才有意义!因此,无非是:
compose([H1|T1],[H2|T2],L3):-
compose(T1,T2,Tail), false,
... .
?- compose(L1, L2, [a,b,c]), false.
组成([H1 | T1]、[H2 | T2]、L3):-
合成(T1,T2,尾部),假,
... .
-组成(L1,L2,[a,b,c]),假。
在这个小程序中,compose/3
的第三个参数被完全忽略。没有人想要L3
。因此,L3
对终止没有影响。要使此终止,我们需要在目标之前以某种方式约束L3
。这本书告诉你怎么做
(此方法适用于纯Prolog程序的任何非终止问题,有关更多信息,请参阅。)首先将其作为更简单但完全等效的方法重新编写
compose([]、[]、[])。%这里有些冗余
组成(L1,[],L1)。
组成([],L2,L2)/*
组成([H1 | T1]、[H2 | T2]、L3):-%整个溶液
组成(T1,T2,尾部),
水头=[H1,H2],
L3=[头|尾]*/
现在可以清楚地看出问题在于递归,首先计算结果的其余部分(Tail
),然后完成它(如L3
)
相反,扭转它
compose([H1 | T1],[H2 | T2],[H1,H2 | Tail]):-%单步
组成(T1,T2,尾部)。
现在我们有了共递归,并且是一个有效的递归。它首先创建结果的开始部分(肯定是有限的),然后填充缺少的部分
(在上面,“creates”可以与“consumes”互换,这是Prolog的双向性质。作为一个单步,它不关心哪些参数被使用,哪些参数被产生)。你所说的等价是什么意思?原始程序在编写(1,[],L)时失败,但您的程序成功。所以,至少在这个特定的情况下,它们是不同的。像往常一样,我对类型很马虎,忽略了错误的键入错误,试图解决问题的核心。因此,如果谓词按预期使用,我想这是等价的。在程序不正确的情况下,“按预期”的模糊概念有多大帮助?不终止的原因很可能是这些附件中的一个,我看不出您可以跳过它们的原因。(顺便说一句,我不是这样的。)添加
L1=[| |]
以避免像compose([a],[b],L)
这样的查询的冗余。请参见:
compose([H1|T1],[H2|T2],L3):-
compose(T1,T2,Tail), false,
... .
?- compose(L1, L2, [a,b,c]), false.