【问题标题】:Remove duplicates in list (Prolog)删除列表中的重复项(Prolog)
【发布时间】:2011-01-16 16:14:07
【问题描述】:

我对 Prolog 完全陌生,并尝试了一些练习。其中之一是:

写一个谓词集(InList,OutList) 它将任意输入作为输入 列表,并返回一个列表,其中每个 输入列表的元素只出现 一次。

这是我的解决方案:

member(X,[X|_]).
member(X,[_|T]) :- member(X,T).

set([],[]).
set([H|T],[H|Out]) :-
    not(member(H,T)),
    set(T,Out).
set([H|T],Out) :-
    member(H,T),
    set(T,Out).

我不允许使用任何内置谓词(最好不要使用not/1)。问题是,set/2 提供了多个 same 解决方案。输入列表中的重复次数越多,产生的解决方案就越多。我究竟做错了什么?提前致谢。

【问题讨论】:

    标签: list prolog


    【解决方案1】:

    由于 Prolog 的回溯,您获得了多种解决方案。从技术上讲,提供的每个解决方案都是正确的,这就是生成它的原因。如果您只想生成一个解决方案,您将不得不在某个时候停止回溯。这就是 Prolog cut 的用途。您可能会发现阅读这些内容可以帮助您解决这个问题。

    更新:没错。如果第一个变量在第二个变量中的多个位置,则您的 member() 谓词以几种不同的方式评估为 true

    我为这个谓词使用了名称mymember(),以免与GNU Prolog 的内置member() 谓词冲突。我的知识库现在看起来像这样:

    mymember(X,[X|_]).
    mymember(X,[_|T]) :- mymember(X,T).
    
    not(A) :- \+ call(A).
    
    set([],[]).
    set([H|T],[H|Out]) :-
        not(mymember(H,T)),
        set(T,Out).
    set([H|T],Out) :-
        mymember(H,T),
        set(T,Out).
    

    因此,mymember(1, [1, 1, 1]). 以三种不同的方式评估为 true

    | ?- mymember(1, [1, 1, 1]).
    
    true ? a
    
    true
    
    true
    
    no
    

    如果你只想得到一个答案,你将不得不使用一个cut。将mymember() 的第一个定义更改为:

    mymember(X,[X|_]) :- !.
    

    解决您的问题。

    此外,如果您愿意,可以通过自己定义 notamember() 谓词来完全避免 not()。选择权在你。

    【讨论】:

    • 本书末尾讨论的剪辑。我还没有读到它们。那么,如果这个谓词给出了多个相同的答案并且它们都是正确的,从技术上讲,我解决了这个练习吗? :)
    • 好的,我的 Prolog 生锈了,我手头没有环境,但我看不出在这种特殊情况下你是如何获得多个结果的。我假设您调用 set() 时第一个变量绑定和第二个未绑定。在这种情况下,我希望 set() 仅当 not() 或 member() 产生多个结果时才会为第二个变量产生多个值,而且我不相信两者都会。一步一步地跟踪评估,看看你的多个结果来自哪里,你会得到比我目前所能提供给你的更多的洞察力。
    • 我现在已经找到并安装了 GNU Prolog,它没有 not() 谓词。您能否概述一下您的 not() 谓词是如何定义的?
    • 谢谢,现在我明白了为什么它会得到多个答案! :)
    • 感谢蒂姆的帮助。我学习了运算符 \+ 的实际用法,并且由于多个响应,我也使用了 cut 运算符。
    【解决方案2】:

    你在正确的轨道上...... 保持纯洁---这很容易!

    使用具体的相等谓词=/3dif/3if_/3 结合使用,在Prolog union for A U B U C 中实现:

    =(X, Y, R) :- X == Y,    !, R = true.
    =(X, Y, R) :- ?=(X, Y),  !, R = false. % syntactically different
    =(X, Y, R) :- X \= Y,    !, R = false. % semantically different
    =(X, Y, R) :- R == true, !, X = Y.
    =(X, X, true).
    =(X, Y, false) :-
       dif(X, Y).
    
    % dif/3 is defined like (=)/3
    dif(X, Y, R) :- X == Y,    !, R = false.
    dif(X, Y, R) :- ?=(X, Y),  !, R = true. % syntactically different
    dif(X, Y, R) :- X \= Y,    !, R = true. % semantically different
    dif(X, Y, R) :- R == true, !, X \= Y.
    dif(X, Y, true) :-         % succeed first!
       dif(X, Y).
    dif(X, X, false).
    
    if_(C_1, Then_0, Else_0) :-
       call(C_1, Truth),
       functor(Truth,_,0),  % safety check
       ( Truth == true -> Then_0 ; Truth == false, Else_0 ).
    

    基于这些谓词,我们构建了一个具体的成员谓词list_item_isMember/3。它在语义上与@false 的memberd_truth/3 等价。我们重新排列参数顺序,因此列表是第一个参数。这将启用 first-argument 索引,从而防止留下无用的选择点,因为 memberd_truth/3 会创建。

    list_item_isMember([],_,false).
    list_item_isMember([X|Xs],E,Truth) :-
       if_(E = X, Truth = true, list_item_isMember(Xs,E,Truth)).
    
    list_set([],[]).
    list_set([X|Xs],Ys) :-
        if_(list_item_isMember(Xs,X), Ys = Ys0, Ys = [X|Ys0]),
        list_set(Xs,Ys0).
    

    一个简单的查询表明所有多余的答案都已消除并且目标成功没有留下任何选择点

    ?- list_set([1,2,3,4,1,2,3,4,1,2,3,1,2,1],Xs)。 Xs = [4,3,2,1]。 % 确定性地成功

    编辑 2015-04-23

    @Ludwig 对set/2 的回答启发了我,如下所示:

    set([],[]).
    set([H|T],[H|T1]) :- subtract(T,[H],T2), set(T2,T1).
    

    SWI-Prolog 的内置谓词subtract/3 可以是非单调的,这可能会限制其使用。 list_item_subtracted/3 是它的单调变体:

    list_item_subtracted([],_,[]).
    list_item_subtracted([A|As],E,Bs1) :-
        if_(dif(A,E), Bs1 = [A|Bs], Bs = Bs1),
        list_item_subtracted(As,E,Bs).
    

    list_setB/2 类似于set/2,但基于list_item_subtracted/3---不是subtract/3

    list_setB([],[]).
    list_setB([X|Xs1],[X|Ys]) :-
        list_item_subtracted(Xs1,X,Xs),
        list_setB(Xs,Ys).
    

    以下查询比较list_set/2list_setB/2

    ?- list_set([1,2,3,4,1,2,3,4,1,2,3,1,2,1], Xs)。 Xs = [4,3,2,1]。 % 确定性地成功 ?- list_setB([1,2,3,4,1,2,3,4,1,2,3,1,2,1],Xs)。 Xs = [1,2,3,4]。 % 确定性地成功 ?- list_set(Xs,[a,b])。 Xs = [a,b] ; Xs = [a,b,b] ; Xs = [a,b,b,b] ... % 不会普遍终止 ?- list_setB(Xs,[a,b])。 Xs = [a,b] ; Xs = [a,b,b] ; Xs = [a,b,b,b] ... % 不会普遍终止

    【讨论】:

    • 为什么有选择点打开(在最终查询中)?似乎memberd_truth/2 需要更好的实现。
    【解决方案3】:

    一个更简单(并且可能更快)的解决方案是使用库谓词 sort/2 来删除 O(n log n) 中的重复项。绝对适用于 Yap prolog 和 SWIPL

    【讨论】:

    • 这里的目标是学习prolog,而不是学习如何使用库函数;)
    • 请注意:此方法简单有效 :) 虽然它有一些副作用,但如果您的目标只是查找顺序无关紧要的结果列表,请使用它 :)
    • @Pierre-LouisGottfrois 我一直在寻找其他东西,但我需要说的是:没有人通过重新实现 很差 库谓词来掌握编程语言。难怪大多数学生讨厌 Prolog。
    • @user1812457 说“难怪大多数学生讨厌 Prolog”作为 可怜 论点。相反,我会说,如果大多数学生确实讨厌 Prolog,这是对这种编程语言的智力兴趣的一个很好的信号。
    【解决方案4】:

    我认为更好的方法是:

    set([], []).
    set([H|T], [H|T1]) :- subtract(T, [H], T2), set(T2, T1).
    

    所以,例如?- set([1,4,1,1,3,4],S) 给你作为输出:

    S = [1, 4, 3]
    

    【讨论】:

    • 是的,代码适用于 SWI-Prolog...,感谢您对我的解决方案的重视。
    • 自言自语? ;-)
    【解决方案5】:

    将我的答案添加到这个旧线程:

    notmember(_,[]).
    notmember(X,[H|T]):-X\=H,notmember(X,T).
    
    set([],[]).
    set([H|T],S):-set(T,S),member(H,S).
    set([H|T],[H|S]):-set(T,S),not(member(H,S)).
    

    此解决方案的唯一优点是它仅使用在此练习出现在original text 中的那一点已经引入的谓词。

    【讨论】:

      【解决方案6】:

      这在没有剪切的情况下有效,但它需要更多的行和另一个参数。 如果我在第三行将 [H2|T2] 更改为 S,它将产生多个结果。我不明白为什么。

      setb([],[],_).
      setb([H|T],[H|T2],A) :- not(member(H,A)),setb(T,T2,[H|A]).
      setb([H|T],[H2|T2],A) :- member(H,A),setb(T,[H2|T2],A).
      setb([H|T],[],A) :- member(H,A),setb(T,[],A).
      set(L,S) :- setb(L,S,[]).
      

      【讨论】:

        【解决方案7】:

        你只需要停止 Prolog 的回溯。

        enter code here
        member(X,[X|_]):- !.
        member(X,[_|T]) :- member(X,T).
        
        set([],[]).
        set([H|T],[H|Out]) :-
           not(member(H,T)),
           !,
        set(T,Out).
        set([H|T],Out) :-
           member(H,T),
           set(T,Out).
        

        【讨论】:

          【解决方案8】:

          使用 Tim 的支持函数 mymember,如果集合中元素的顺序不重要,您可以这样做:

          mymember(X,[X|_]).
          mymember(X,[_|T]) :- mymember(X,T).
          
          mkset([],[]).
          mkset([T|C], S) :- mymember(T,C),!, mkset(C,S).
          mkset([T|C], S) :- mkset(C,Z), S=[T|Z].
          

          所以,例如?- mkset([1,4,1,1,3,4],S) 给你作为输出:

          S = [1, 3, 4]
          

          但是,如果您想要一个具有列表中排序元素的集合,您可以使用:

          mkset2([],[], _).
          mkset2([T|C], S, D) :- mkset2(C,Z,[T|D]), ((mymember(T,D), S=Z,!) ; S=[T|Z]).
          mkset(L, S) :- mkset2(L,S,[]).
          

          这个解决方案,与上一个例子的输入相同,给你:

          S = [1, 4, 3]
          

          这次元素的顺序与它们在输入列表中出现的顺序相同。

          【讨论】:

            【解决方案9】:
            /* Remove duplicates from a list without accumulator */
            our_member(A,[A|Rest]).
            our_member(A, [_|Rest]):-
                our_member(A, Rest).
            
            remove_dup([],[]):-!.
            remove_dup([X|Rest],L):-
                our_member(X,Rest),!,
                remove_dup(Rest,L).
            remove_dup([X|Rest],[X|L]):-
                remove_dup(Rest,L).
            

            【讨论】:

            • @rkta 这不是一个优化的解决方案。它对较小的列表有效,但随着列表长度的增加,搜索空间变得更大。我们可以使用累加器对其进行优化。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-12-19
            • 1970-01-01
            • 2011-10-04
            • 1970-01-01
            • 2014-09-30
            • 2011-01-13
            相关资源
            最近更新 更多