【问题标题】:Deleting all occurrences of an element from a list从列表中删除所有出现的元素
【发布时间】:2012-08-29 10:00:37
【问题描述】:

试图编写一个给定一个值和一个列表的过程,它会删除该列表中所有出现的值:

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]).

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

我怎样才能让它在这两种情况下都起作用?

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

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

但它不起作用。

【问题讨论】:

    标签: prolog prolog-dif


    【解决方案1】:

    使用剪辑

    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 的初学者,您应该尽量避免使用 cut。只要您不了解递归和逻辑编程的其他基础知识,它只会增加您的误解。

    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 统一。

    注意,还有一种方法不能使用谓词,即+, -, +,正如cTI 告诉我们的那样。所以像?- delMember(1, R, [2, 3]). 这样的查询将与我的版本一起循环。

    希望对你有用。

    【讨论】:

    • 谢谢,我有一个问题。如果我查询delMember(Y, [1,2,3,1,2,3], X),我只会得到一个解决方案(Y=1,X=[2,3,2,3])。为什么我也没有得到 Y=2 和 Y=3 ?
    • 那是因为我们使用(\=)/2,这并不适合这份工作。这导致 Prolog 只能使用第二个子句来查找 X。尝试使用dif(X, T) 而不是X \= T
    • 伟大的收获! dif/2 这是一个非常奇怪的野兽,这很好地利用了它。
    • 是的,仍然是 +、-、+ 循环。不是最好的解决方案!
    【解决方案2】:

    这不是一个真正的答案,只是对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 所述。

    但是这些 sn-ps 都不能解决一般情况:

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

    【讨论】:

    • @thanosQR:我从你的回答中逐字删除了 del/3。产生的输出似乎错误; X = 1, Y = [1, 2] ;
    • 哦,对了,我现在明白了! (给人的印象是您发布的输出是解决方案应该具有的)。事实上,\==/2 实际上并没有对 X 施加限制,它可以稍后与 1 统一。我认为这可以通过反转正文中的规则来解决(del(X, [H|L1], [H|L2]):- del(X,L1,L2), X\==H.
    • 谢谢 :) 编辑了我的帖子并添加了尾递归版本(我希望它不会改变语义)。
    • 即使不是尾递归,也已经比 dif/2 版本快了:?- time(forall((between(1,30,N),length(X,N),delMember(1,X,[2,3,2,3])),true)). % 2,557,034 inferences, 1,002 CPU in 1,003 seconds (100% CPU, 2551178 Lips) true. ?- time(forall((between(1,30,N),length(X,N),del2(1,X,[2,3,2,3])),true)). % 1,319,018 inferences, 0,671 CPU in 0,672 seconds (100% CPU, 1965375 Lips)。 del2 这是你的版本,delMember 使用 dif/2
    • 事实上,尾递归版本实际上(有点)慢:?- time(forall((between(1,30,N),length(X,N),del0(1,X,[2,3,2,3])),true)). % 1,318,958 inferences, 0.292 CPU in 0.297 seconds (98% CPU, 4511074 Lips)time(forall((between(1,30,N),length(X,N),del_tr(1,X,[2,3,2,3])),true)). % 1,318,957 inferences, 0.307 CPU in 0.307 seconds (100% CPU, 4292830 Lips)
    【解决方案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 可以合并到包装谓词中,因为它不会影响其他实例化模式。

    【讨论】:

      【解决方案4】:

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

      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. 然后对于基于 1) 构建的列表中的每个 X,从输入列表中丢弃此元素,以获得没有成员 X 的输出列表 Z。

      第 1 步是使用 setof(X, member(X, Y), L) 完成的,它有两种工作方式。当参数X 已经实例化时,如果X 包含在输入参数Y 中,那么L 将是列表[X],如果X 不包含在Y 中,它将失败。另一方面,如果X 未被实例化,则L 将是输入参数Y 的不同元素的集合。

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

      请注意,如果在调用过程时未实例化参数 X,则在回溯 member(X, Y) 时,我们将获取输入列表 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].
      

      【讨论】:

      • 我认为这个答案促进了非常糟糕的做法,尤其是在针对初学者的帖子中。
      • @Mog:不知道你为什么这么说......也许是因为我没有解释代码?这个实现并没有真正使用 Prolog 的任何讨厌的特性......
      • 顺便说一句,?- delMember(S, [1, 2], R). S = 1, R = [2] ; S = 2, R = [1]. 没有给出正确的答案R = [1, 2], dif(S, 1), dif(S, 2)。查看我的编辑。
      • 根据 OP 的问题,您提供的最后一个答案 (R = [1, 2], dif(X, 1), dif(X, 2)) 是错误的。 OP 说 delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,3,1,2,3,1,2,3]). 应该失败。
      • 好吧 OP 说 true 不正确,可能是因为它没有提供任何有意义的绑定。正确的解决方案可以。并且在 OP 的其余部分中指定的 del 的自然语义指向正确的绑定是正确的。
      猜你喜欢
      • 2017-04-12
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 2011-05-11
      • 1970-01-01
      • 1970-01-01
      • 2016-03-23
      相关资源
      最近更新 更多