从列表中删除所有出现的元素

时间:2012-08-29 10:00:38

标签: prolog prolog-dif

尝试编写一个给定值和列表的过程,它会删除列表中写入该值的所有内容:

delMember(X, [], []) :- !.
delMember(X, [X|Xs], Y) :- !, delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- !, delMember(X, Xs, Y2), append([T], Y2, Y).

由于cut此代码无法正确回答如下问题:

delMember(Y, [1,2,3,1,2,3,1,2,3], [1, 2, 1, 2, 1, 2 ]).

如果删除剪辑:

delMember(X, [], []).
delMember(X, [X|Xs], Y) :- delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- delMember(X, Xs, Y2), append([T], Y2, Y).

在以下查询中失败:

delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,3,1,2,3,1,2,3]).

(当正确答案为true时返回false。)

如何让它在两种情况下均有效?

也许我可以在第三行代码中检查X is not T,我试过了:

delMember(X, [T|Xs], Y) :- not(X = T), delMember(X, Xs, Y2), append([T], Y2, Y).

但它不起作用。

4 个答案:

答案 0 :(得分:7)

使用剪切

delMember(X, [], []) :- !.
delMember(X, [X|Xs], Y) :- !, delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- !, delMember(X, Xs, Y2), append([T], Y2, Y).

在这里,您可以看到在谓词的最后一个句子中使用!/0。这不是必需的。在最后一个条款之后,没有剩下的选择了(Prolog记住了从左到右,从上到下的选择点),因此切割(删除选项)不会有用,因为你已经在列表的底部选择。

为了说明,请参阅

a :- b; c.
a :- d.

在此,为了证明a,Prolog将首先尝试b,然后c,然后d(从左到右,然后从上到下)。

顺便说一句,作为Prolog的初学者,你应该完全避免使用剪辑。只要你没有得到递归和逻辑编程的其他基础知识,它只会增加你的误解。

Prolog递归

除了这个小小的说明,你的问题是你还没有正确理解Prolog的递归。请参阅已经解决此问题的this answer的第一部分。

你的第三个条款错了:

delMember(X, [T|Xs], Y) :- delMember(X, Xs, Y2), append([T], Y2, Y).

应该是:

delMember(X, [T|Xs], [T|Y]) :- delMember(X, Xs, Y).

嗯,这不是真的错,它只是非常不理想。它不是尾递归的,而是使用append/3,它会将线性谓词转换为二次谓词。另外,正如您所注意到的那样,由于它不是尾递归的,因此在某些情况下终止更难获得。

然后,要删除使用剪切!/0,您可以考虑在最后一个子句中添加 guard

delMember(_, [], []).
delMember(X, [X|Xs], Y) :-
    delMember(X, Xs, Y).
delMember(X, [T|Xs], [T|Y]) :-
    dif(X, T),
    delMember(X, Xs, Y).

警卫dif(X, T)指定如果我们处于第三种情况,则我们不能同时在第二种情况下:X无法与此处T统一。< / p>

请注意,我们仍然有一种方法无法使用谓词,+, -, +就像cTI告诉我们的那样。因此?- delMember(1, R, [2, 3]).之类的查询会循环显示我的版本。

我希望它有用。

答案 1 :(得分:4)

这不是一个真正的答案,只是对MogthanosQR答案的扩展说明,太长而无法发表评论。 这样的答案是令人愉快和有益的,但需要重新考虑削减。考虑:

delMember(_, [], []).
delMember(X, [X|Xs], Y) :-
    delMember(X, Xs, Y), !.
delMember(X, [T|Xs], [T|Y]) :-
    delMember(X, Xs, Y).

此定义允许

?- delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,1,2,1,2]).
Y = 3.

由于最后一个原因的警卫而导致原始Mog'代码失败。值得注意的是,用X \== T替换保护(将测试限制为匹配的实例化状态),也解决了这个问题,如thanosQR所述。

但这些片段都没有解决一般情况:

?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
X = 1,
Y = [1, 2] ;
Y = [1, 2, 1].

答案 2 :(得分:3)

让我们做一些改写:因为你想在多个实例化模式中使用谓词“给出一个值和一个列表的过程,它会删除所有出现的值列表“没有定义它在其他情况下应该如何表现。所以,我们可能想要“如果第二个和第三个参数是列表L1,则谓词是真的,如果我们忽略第一个参数的所有出现,则L2和L1与L2是相同的列表”

现在,有两种方法可以编写具有多个可能实例化的谓词;您可以使用var/1ground/1之类的元逻辑谓词并为每个编写代码(这可能允许您编写针对该特定实例进行优化的代码)或编写将定义属性的代码逻辑上(这可能更具挑战性)。

在这种情况下,我们可以做类似的事情:

    del(_, [], []).
    del(X, [X|L1], L2):-
        del(X,L1,L2).
    del(X, [H|L1], [H|L2]):-
        X\==H,
        del(X,L1,L2).

具有以下行为:

19 ?- del(1, [1,2,3], X).
X = [2, 3] ;
false.
1,2,3,
20 ?- del(1, [1,2,3], [2,3]).
true ;
false.

21 ?- del(X, [1,2,3], [2,3]).
X = 1 ;
false.

22 ?- del(X, [1,2,3], Y).
X = 1,
Y = [2, 3] ;
X = 2,
Y = [1, 3] ;
X = 3,
Y = [1, 2] ;
Y = [1, 2, 3] ;
false.

23 ?- del(X, P, Y).
P = Y, Y = [] ;
P = [X],
Y = [] ;
P = [X, X],
Y = [] ;
P = [X, X, X],
Y = [] ;
P = [X, X, X, X],
Y = [] ;
P = [X, X, X, X, X],
Y = [] ;
P = [X, X, X, X, X, X],
Y = [] .

关于最后一次通话; prolog返回增长的X列表,因为使用了深度优先算法;通过使用length/2,我们可以得到广度优先的结果(_G表示变量未实例化(可以是任何东西)):

24 ?- length(P,N), del(X, P, Y).
P = [],
N = 0,
Y = [] ;
P = [X],
N = 1,
Y = [] ;
P = [_G548],
N = 1,
Y = [_G548] ;
P = [X, X],
N = 2,
Y = [] ;
P = [X, _G551],
N = 2,
Y = [_G551] ;
P = [_G548, X],
N = 2,
Y = [_G548] ;
P = [_G548, _G551],
N = 2,
Y = [_G548, _G551] ;
P = [X, X, X],

编辑:正如@chac所指出的,如果第一个列表(至少)有一个重复元素,则上面的谓词行为不正确:

?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
X = 1,
Y = [1, 2] ;                  <----- wrong
Y = [1, 2, 1].

这是因为\==/2\=/2实际上并没有对变量施加限制。 这可以通过在第三个子句中切换规则的顺序来解决:

    del(_, [], []).
    del(X, [X|L1], L2):-
        del(X,L1,L2).
    del(X, [H|L1], [H|L2]):-
        del(X,L1,L2),
        X\==H.


4 ?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
Y = [1, 2, 1] ;
false.
然而,

这意味着谓词不再是尾递归的。为了解决这个问题,我们可以保留X不应该的值列表:

del(X,L1,L2):-
    del(X,L1,L2,[]).

del(X, [], [], NotX):-
    \+ member(X,NotX).
del(X, [X|L1], L2, NotX):-
    del(X,L1,L2,NotX).
del(X, [H|L1], [H|L2], NotX):-
    X\==H,     % <--- optional; stops the execution earlier (saving time)
    del(X,L1,L2,[H|NotX]).

但是,根据以下内容,尾递归版实际上更慢:

?-time(forall((between(1,50,N),length(X,N),del2(1,X,[2,3,2,3])),true)).
% 25,600,793 inferences, 5.468 CPU in 5.548 seconds (99% CPU, 4682134 Lips)
true.

?- time(forall((between(1,50,N),length(X,N),del_tr(1,X,[2,3,2,3])),true)).
% 37,346,143 inferences, 6.426 CPU in 6.428 seconds (100% CPU, 5811563 Lips)
true.

仍然,+ - +不起作用(它处于无限循环中)。但为什么?问题在于条款的顺序: del(1,L1,[2])将首先应用将“X”添加到L1的头部的规则,然后永远应用相同的规则。 这可以通过使用(再次)length/2

来解决
?- length(X,2), del(1,X,[2]).
X = [1, 2] ;
X = [2, 1] ;
false.

或者我们可以改变子句的顺序:

del(_, [], []).
del(X, [H|L1], [H|L2]):-
    X\==H,
    del(X,L1,L2),
    X\==H.
del(X, [X|L1], L2):-
    del(X,L1,L2).

然而length/2可能会再次有用,因为没有它prolog会进行深度优先搜索:

?- del(1,X,[2]).
X = [2] ;
X = [2, 1] ;
X = [2, 1, 1] ;
X = [2, 1, 1, 1] ;
X = [2, 1, 1, 1, 1] ;
X = [2, 1, 1, 1, 1, 1] ;
X = [2, 1, 1, 1, 1, 1, 1] 

当然length/2可以包含在包装谓词中,因为它不会影响其他实例化模式。

答案 3 :(得分:0)

这里有一个片段,它也适用于未实例化的第一个和第三个参数:

delMember(X, Y, Z):-
  bagof(A, (setof(X, member(X, Y), L), member(X, L), member(A, Y), A\==X), Z).

我将在这里解释这段代码的作用。我的想法是:

  1. 构建输入列表Y的不同成员列表,该列表与输入成员X
  2. 统一
  3. 然后对于构建于1的列表中的每个X)从输入列表中丢弃此元素以获得没有成员X的输出列表Z.
  4. 第1步是使用setof(X, member(X, Y), L)完成的,它有两种方式。当参数X已经实例化时,如果输入参数L中包含[X]X将是列表Y,如果X将失败Y 1}}未包含在X中。另一方面,如果L未实例化,则Y将是输入参数L的不同元素集。

    现在在第2步中,我们回溯了Y的每个元素,对于此列表的每个成员,我们从输入列表Z中过滤此元素,从而产生结果。我们在输出列表member(X, Y)中收集所有这些元素。

    请注意,当调用过程时,如果参数X未被实例化,则在回溯Y时,我们将获得将用于过滤的输入列表?- delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,3,1,2,3,1,2,3]). false. ?- delMember(Y, [1,2,3,1,2,3], X). Y = 1, X = [2, 3, 2, 3] ; Y = 2, X = [1, 3, 1, 3] ; Y = 3, X = [1, 2, 1, 2]. ?- delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,1,2,1,2]). Y = 3. ?- delMember(X,[1,2,1],Y). X = 1, Y = [2] ; X = 2, Y = [1, 1]. 的每个成员。

    测试用例:

    {{1}}