Prolog 错误:超出全局堆栈

Prolog 错误:超出全局堆栈,prolog,Prolog,我试图创建一个谓词,每次输出所有列表减去一个元素。对于列表[a,b,c],我需要谓词返回 a, [b, c] b, [a, c] c, [a, b] 我创建了以下谓词: elem_list(Elems, X, ElemsNoX) :- append(ElemsA, [X], ElemsAX), append(ElemsAX, ElemsB, Elems), append(ElemsA, ElemsB, ElemsNoX). 这似乎有效,但在尝试执行时,我得到以下错误:

我试图创建一个谓词,每次输出所有列表减去一个元素。对于列表[a,b,c],我需要谓词返回

a, [b, c]
b, [a, c]
c, [a, b]
我创建了以下谓词:

elem_list(Elems, X, ElemsNoX) :-
    append(ElemsA, [X], ElemsAX),
    append(ElemsAX, ElemsB, Elems),
    append(ElemsA, ElemsB, ElemsNoX).
这似乎有效,但在尝试执行时,我得到以下错误:

?- elem_list([a,b,c], X, L).
X = a,
L = [b, c] ;
X = b,
L = [a, c] ;
X = c,
L = [a, b] ;
ERROR: Out of global stack

这个错误意味着什么?如何解决它呢?

在大多数Prolog版本中,您可以使用
跟踪。
启用一种模式,在该模式下,Prolog解释器执行的每个步骤都会被打印出来。为谓词启用
跟踪。
时,将显示:

[trace]  ?- elem_list([a,b,c], X, L).
   Call: (7) elem_list([a, b, c], _G25166352, _G25166353) ? creep
   Call: (8) lists:append(_G25166456, [_G25166352], _G25166458) ? creep
   Exit: (8) lists:append([], [_G25166352], [_G25166352]) ? creep
   Call: (8) lists:append([_G25166352], _G25166457, [a, b, c]) ? creep
   Exit: (8) lists:append([a], [b, c], [a, b, c]) ? creep
   Call: (8) lists:append([], [b, c], _G25166353) ? creep
   Exit: (8) lists:append([], [b, c], [b, c]) ? creep
   Exit: (7) elem_list([a, b, c], a, [b, c]) ? creep
X = a,
L = [b, c] ;
   Redo: (8) lists:append(_G25166456, [_G25166352], _G25166458) ? creep
   Exit: (8) lists:append([_G25166449], [_G25166352], [_G25166449, _G25166352]) ? creep
   Call: (8) lists:append([_G25166449, _G25166352], _G25166463, [a, b, c]) ? creep
   Exit: (8) lists:append([a, b], [c], [a, b, c]) ? creep
   Call: (8) lists:append([a], [c], _G25166353) ? creep
   Exit: (8) lists:append([a], [c], [a, c]) ? creep
   Exit: (7) elem_list([a, b, c], b, [a, c]) ? creep
X = b,
L = [a, c] ;
   Redo: (8) lists:append([_G25166449|_G25166450], [_G25166352], [_G25166449|_G25166453]) ? creep
   Exit: (8) lists:append([_G25166449, _G25166455], [_G25166352], [_G25166449, _G25166455, _G25166352]) ? creep
   Call: (8) lists:append([_G25166449, _G25166455, _G25166352], _G25166469, [a, b, c]) ? creep
   Exit: (8) lists:append([a, b, c], [], [a, b, c]) ? creep
   Call: (8) lists:append([a, b], [], _G25166353) ? creep
   Exit: (8) lists:append([a, b], [], [a, b]) ? creep
   Exit: (7) elem_list([a, b, c], c, [a, b]) ? creep
X = c,
L = [a, b] ;
   Redo: (8) lists:append([_G25166449, _G25166455|_G25166456], [_G25166352], [_G25166449, _G25166455|_G25166459]) ? creep
   Exit: (8) lists:append([_G25166449, _G25166455, _G25166461], [_G25166352], [_G25166449, _G25166455, _G25166461, _G25166352]) ? creep
   Call: (8) lists:append([_G25166449, _G25166455, _G25166461, _G25166352], _G25166475, [a, b, c]) ? creep
   Fail: (8) lists:append([_G25166449, _G25166455, _G25166461, _G25166352], _G25166475, [a, b, c]) ? creep
因此我们看到第一个
append/3
调用(注意
[\u g2516352]
作为第二个参数)。只生成新的变量。我们可以通过执行一个单独的调用来确认这一点:

?- append(ElemsA, [X], ElemsAX).
ElemsA = [],
ElemsAX = [X] ;
ElemsA = [_G25167627],
ElemsAX = [_G25167627, X] ;
ElemsA = [_G25167627, _G25167633],
ElemsAX = [_G25167627, _G25167633, X] ;
ElemsA = [_G25167627, _G25167633, _G25167639],
ElemsAX = [_G25167627, _G25167633, _G25167639, X]
因此,使用append时,左侧和右侧都有未接地变量的列表。当然,这些列表不可能完全固定:这些列表包含的元素比初始列表的元素更多,这意味着这些元素最终会在过程中失败(我们可以在进一步跟踪程序时看到这一点)。但是很明显,第一个谓词并不知道这一点,因此只是继续构造列表,直到内存耗尽为止

因此,上述解决方案不是一个好办法

例如,我们可以编写一个成功的自定义谓词:

exclude([],_,[]).
exclude([H|T],H,T).
exclude([H|T],X,[H|T2]) :-
    exclude(T,X,T2).
代码的工作原理如下:如果第一个参数与
[]
(空列表)相一致,则返回空列表,并将要排除的部分保留为未舍入。如果第一个参数与
[H | T]
相结合(一个带有头
H
和尾
T
的列表),则有两个选项:我们选择头
H
作为要排除的元素,然后返回尾
T
,或者我们推迟排除,让递归调用选择一个元素

上述计划将允许我们排除任何元素:

因此,最后一行将
X
保留为不接地,并返回整个列表
[a,b,c]

如果您不想这样做,只需删除第一行:

exclude1([H|T],H,T).
exclude1([H|T],X,[H|T2]) :-
    exclude1(T,X,T2).
现在Prolog被迫将第一个列表中的至少一个元素作为要排除的元素。这将产生:

?- exclude1([a,b,c], X, L).
X = a,
L = [b, c] ;
X = b,
L = [a, c] ;
X = c,
L = [a, b] ;
false.

这意味着你进入了无限递归。@WillemVanOnsem,但我没有使用递归。这是不必要的,
append/3
是递归生成的。问题是你的
append/3
调用有无限多个可能的解决方案供Prolog尝试,以查看它是否解决了你的问题。如果允许使用预定义谓词,为什么不直接使用
select/3
elem_列表(list,X,NoX):-select(X,list,NoX)。
这是一个很好的解释,解释了为什么会发生这种情况,但是对于提供的代码有一个更简单的修复方法:
prolog elem_列表(Elems,X,ElemsNoX):-append(ElemsAX,ElemsB,Elems),append(ElemsA,[X],ElemsAX),append(ElemsA,ElemsB,ElemsNoX)通过切换求值顺序,我们可以在生成的列表上强制设置边界。我们知道它们必须是原始的,所以这是一个直观的界限,可以防止无限递归。
?- exclude1([a,b,c], X, L).
X = a,
L = [b, c] ;
X = b,
L = [a, c] ;
X = c,
L = [a, b] ;
false.