想想谓词应该描述什么。它是列表、数字和等于第一个或缺少指定数量的第一个元素的列表之间的关系。让我们为它取一个描述性的名称,比如 list_n_removed/3。由于您希望删除许多相同的元素,因此出于比较原因,我们保留列表的头部,因此 list_n_removed/3 只是调用谓词和另一个带有附加参数的谓词,我们称之为 list_n_removed_head/4,描述实际关系:
list_n_removed([X|Xs],N,R) :-
list_n_removed_head([X|Xs],N,R,X).
谓词 list_n_removed_head/4 必须处理两种不同的情况:N=0,那么第一个和第三个参数是同一个列表或N>0,那么第一个列表的头部必须等于参考元素(第 4 个参数),并且该关系也必须适用于尾部:
list_n_removed_head(L,0,L,_X).
list_n_removed_head([X|Xs],N,R,X) :-
N>0,
N0 is N-1,
list_n_removed_head(Xs,N0,R,X).
现在让我们看看它是如何工作的。您的示例查询产生了所需的结果:
?- list_n_removed([o,o,o,o],3,R).
R = [o] ;
false.
如果前三个元素不相等,则谓词失败:
?- list_n_removed([o,b,o,o],3,R).
false.
如果列表的长度等于N,则结果为空列表:
?- list_n_removed([o,o,o],3,R).
R = [].
如果列表的长度小于N,则谓词失败:
?- list_n_removed([o,o],3,R).
false.
如果N=0 两个列表相同:
?- list_n_removed([o,o,o,o],0,R).
R = [o, o, o, o] ;
false.
如果N<0 谓词失败:
?- list_n_removed([o,o,o,o],-1,R).
false.
谓词也可以用在另一个方向:
?- list_n_removed(L,0,[o]).
L = [o] ;
false.
?- list_n_removed(L,3,[o]).
L = [_G275, _G275, _G275, o] ;
false.
但是,如果第二个参数是可变的:
?- list_n_removed([o,o,o,o],N,[o]).
ERROR: >/2: Arguments are not sufficiently instantiated
这可以通过使用 CLP(FD) 来避免。考虑以下更改:
:- use_module(library(clpfd)). % <- new
list_n_removed([X|Xs],N,R) :-
list_n_removed_head([X|Xs],N,R,X).
list_n_removed_head(L,0,L,_X).
list_n_removed_head([X|Xs],N,R,X) :-
N #> 0, % <- change
N0 #= N-1, % <- change
list_n_removed_head(Xs,N0,R,X).
现在上面的查询提供了预期的结果:
?- list_n_removed([o,o,o,o],N,[o]).
N = 3 ;
false.
与最一般的查询一样:
?- list_n_removed(L,N,R).
L = R, R = [_G653|_G654],
N = 0 ;
L = [_G653|R],
N = 1 ;
L = [_G26, _G26|R],
N = 2 ;
L = [_G26, _G26, _G26|R],
N = 3 ;
.
.
.
上面的其他查询与 CLP(FD) 版本产生相同的答案。