【问题标题】:PROLOG: Determining if elements in list are equal if order does not matterPROLOG:如果顺序无关紧要,则确定列表中的元素是否相等
【发布时间】:2015-07-21 08:41:57
【问题描述】:

我正在尝试找出一种方法来检查两个列表是否相等,而不管它们的元素顺序如何。

我的第一次尝试是:

areq([],[]).
areq([],[_|_]).
areq([H1|T1], L):- member(H1, L), areq(T1, L).

但是,这只检查左侧列表的所有元素是否存在于右侧列表中;意思是areq([1,2,3],[1,2,3,4]) => true。在这一点上,我需要找到一种能够双向测试事物的方法。我的第二次尝试如下:

areq([],[]).
areq([],[_|_]).
areq([H1|T1], L):- member(H1, L), areq(T1, L), append([H1], T1, U), areq(U, L).

我会尝试在左侧重建 leest 并在最后交换列表;但这失败得很惨。

我的递归意识非常差,根本不知道如何改进它,尤其是使用 Prolog。在这一点上,任何提示或建议将不胜感激。

【问题讨论】:

  • 你所描述的被称为reflexive closure of a relationship
  • 承诺:将为像@Pyetras'这样的纯解决方案提供赏金。 确定和纯解决方案将增加(即进一步的赏金)。

标签: prolog


【解决方案1】:

作为起点,我们来看看@CapelliC 对equal_elements/2 的第二个实现:

equal_elements([], []).
equal_elements([X|Xs], Ys) :-
   select(X, Ys, Zs),
   equal_elements(Xs, Zs).

上面的实现为像这样的查询留下了无用的选择点:

?- equal_elements([1,2,3],[3,2,1]).
true ;                                 % succeeds, but leaves choicepoint
false.

我们能做什么?我们可以通过使用来解决效率问题 selectchk/3 而不是 select/3,但这样做我们会失去我们可以做得更好吗?

我们可以! 介绍selectd/3,一个逻辑纯谓词,结合了selectchk/3 的确定性和select/3 的纯度。 selectd/3 基于 if_/3(=)/3:

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

selectd/3 可以直接替代select/3,所以使用起来很容易!

equal_elementsB([], []).
equal_elementsB([X|Xs], Ys) :-
   selectd(X, Ys, Zs),
   equal_elementsB(Xs, Zs).

让我们看看它的实际效果!

?- equal_elementsB([1,2,3],[3,2,1]).
true.                                  % succeeds deterministically

?- equal_elementsB([1,2,3],[A,B,C]), C=3,B=2,A=1.
A = 1, B = 2, C = 3 ;                  % still logically pure
false.

编辑 2015-05-14

如果谓词,则 OP 不具体 应该强制项目发生在双方 相同的多重性。 equal_elementsB/2 是这样的吗,如这两个查询所示:

?- equal_elementsB([1,2,3,2,3],[3,3,2 ,1,2])。 真的。 ?- equal_elementsB([1,2,3,2,3],[3,3,2 ,1,2,3])。 错误

如果我们希望第二个查询成功,我们可以通过使用元谓词以逻辑上纯粹的方式放宽定义 tfilter/3 和 具体化不平等dif/3:

equal_elementsC([],[]).
equal_elementsC([X|Xs],Ys2) :-
   selectd(X,Ys2,Ys1),
   tfilter(dif(X),Ys1,Ys0),
   tfilter(dif(X),Xs ,Xs0),
   equal_elementsC(Xs0,Ys0).

让我们像上面一样运行两个查询,这次使用equal_elementsC/2

?- equal_elementsC([1,2,3,2,3],[3,3,2 ,1,2])。 真的。 ?- equal_elementsC([1,2,3,2,3],[3,3,2 ,1,2,3])。 是的

编辑 2015-05-17

事实上,equal_elementsB/2 不会在以下情况下普遍终止:

?- equal_elementsB([],Xs),假的。 % 普遍终止 错误的。 ?- equal_elementsB([_],Xs),假的。 % 给出了一个答案,但是...... %%% 永远等待 % ... 不会普遍终止

但是,如果我们翻转第一个和第二个参数,我们会得到终止!

?- equal_elementsB(Xs,[]), 假的。 % 普遍终止 错误的。 ?- equal_elementsB(Xs,[_]), 假的。 % 普遍终止 错误的。

an answer given by @AmiTavory 的启发,我们可以通过“锐化”解决方案集来改进equal_elementsB/2 的实现,如下所示:

equal_elementsBB(Xs,Ys) :- 相同长度(Xs,Ys), equal_elementsB(Xs,Ys)。

为了检查非终止是否消失,我们将使用两个谓词的查询头对头:

?- equal_elementsB([_],Xs),假的。 %%% 永远等待 % 不会普遍终止 ?- equal_elementsBB([_],Xs),假的。 错误的。 % 普遍终止

请注意,相同的“技巧”不适用于equal_elementsC/2, 因为解决方案集的大小是无限的(对于所有感兴趣的最微不足道的实例除外)。

【讨论】:

    【解决方案2】:

    使用sort/2 ISO 标准内置谓词的简单解决方案,假设两个列表都不包含重复元素:

    equal_elements(List1, List2) :-
        sort(List1, Sorted1),
        sort(List2, Sorted2),
        Sorted1 == Sorted2.
    

    一些示例查询:

    | ?- equal_elements([1,2,3],[1,2,3,4]).
    no
    
    | ?- equal_elements([1,2,3],[3,1,2]).    
    yes
    
    | ?- equal_elements([a(X),a(Y),a(Z)],[a(1),a(2),a(3)]).
    no
    
    | ?- equal_elements([a(X),a(Y),a(Z)],[a(Z),a(X),a(Y)]).
    yes
    

    【讨论】:

      【解决方案3】:

      在 Prolog 中,你通常可以完全按照你说的去做

      areq([],_).
      areq([H1|T1], L):- member(H1, L), areq(T1, L).
      
      bi_areq(L1, L2) :- areq(L1, L2), areq(L2, L1).
      

      必要时重命名。

      【讨论】:

      • 谓词member/2使用unification,表示两个列表可能不同(问题是关于元素相等),变成相等当测试它们是否有相等元素时。
      • 这是一个不错且纯粹的解决方案。对它的唯一保留是它留下了开放的选择点,因此不像它可能的那样具有确定性。
      • bi_areq 函数的用途是什么?我没有看到它是从 areq 调用的。你能进一步解释一下吗?
      【解决方案4】:

      紧凑的形式:

      member_(Ys, X) :- member(X, Ys).
      equal_elements(Xs, Xs) :- maplist(member_(Ys), Xs).
      

      但是,使用 member/2 似乎效率低下,并且为重复项(在双方)留下了模棱两可的空间。相反,我会使用 select/3

      ?- [user].
      
      equal_elements([], []).
      equal_elements([X|Xs], Ys) :-
        select(X, Ys, Zs),
        equal_elements(Xs, Zs).
      

      ^D 这里

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

      或者,更好,

      equal_elements(Xs, Ys) :- permutation(Xs, Ys).
      

      【讨论】:

      • 但是select/3 使用统一。因此,如果测试元素相等性,则与member/2 的问题相同。使用库permutation/2也有同样的问题。
      • 更好!但对于决定论来说还不够好! (如适用)
      【解决方案5】:

      其他的答案都很优雅(远高于我自己的 Prolog 级别),但让我印象深刻的是问题陈述

      对常规用途有效。

      接受的答案是O(max(|A| log(|A|), |B|log(|B|))无论列表是否相等(直到排列)或没有

      至少,在麻烦排序之前检查长度是值得的,这会在列表长度不相等的情况下将运行时间减少到与列表长度呈线性关系。

      扩展这一点,使用随机 digests 修改解决方案以使其运行时间在列表不相等(直至排列)的一般情况下有效地线性化并不困难。

      假设我们定义

      digest(L, D) :- digest(L, 1, D).
      digest([], D, D) :- !.
      digest([H|T], Acc, D) :-
          term_hash(H, TH),
          NewAcc is mod(Acc * TH, 1610612741),
          digest(T, NewAcc, D).
      

      这是数学函数的 Prolog 版本 Prod_i h(a_i) | p,其中 h 是哈希,p 是素数。它有效地将每个列表映射到 0, ...., p - 1 范围内的随机(在散列意义上)值(在上面,p 是大素数 1610612741)。

      我们现在可以检查两个列表是否具有相同的摘要:

      same_digests(A, B) :-
          digest(A, DA),
          digest(B, DB),
          DA =:= DB.
      

      如果两个列表具有不同的摘要,则它们不能相等。如果两个列表具有相同的摘要,那么它们不相等的可能性很小,但这仍然需要检查。对于这个案例,我无耻地窃取了 Paulo Moura 的出色答案。

      最后的代码是这样的:

      equal_elements(A, B) :-
          same_digests(A, B),
          sort(A, SortedA),
          sort(B, SortedB),
          SortedA == SortedB.
      
      same_digests(A, B) :-
          digest(A, DA),
          digest(B, DB),
          DA =:= DB.
      
      digest(L, D) :- digest(L, 1, D).
      digest([], D, D) :- !.
      digest([H|T], Acc, D) :-
          term_hash(H, TH),
          NewAcc is mod(Acc * TH, 1610612741),
          digest(T, NewAcc, D).
      

      【讨论】:

        【解决方案6】:

        一种可能性,灵感来自 qsort:

        split(_,[],[],[],[]) :- !.
        split(X,[H|Q],S,E,G) :-
            compare(R,X,H),
            split(R,X,[H|Q],S,E,G).
        
        split(<,X,[H|Q],[H|S],E,G) :-
            split(X,Q,S,E,G).
        split(=,X,[X|Q],S,[X|E],G) :-
            split(X,Q,S,E,G).
        split(>,X,[H|Q],S,E,[H|G]) :-
            split(X,Q,S,E,G).
        
        
        cmp([],[]).
        cmp([H|Q],L2) :-
            split(H,Q,S1,E1,G1),
            split(H,L2,S2,[H|E1],G2),
            cmp(S1,S2),
            cmp(G1,G2).
        

        【讨论】:

          【解决方案7】:

          使用 cut 的简单解决方案。

          areq(A,A):-!.
          areq([A|B],[C|D]):-areq(A,C,D,E),areq(B,E).
          areq(A,A,B,B):-!.
          areq(A,B,[C|D],[B|E]):-areq(A,C,D,E).
          

          一些示例查询:

          ?- areq([],[]).
          true.
          
          ?- areq([1],[]).
          false.
          
          ?- areq([],[1]).
          false.
          
          ?- areq([1,2,3],[3,2,1]).
          true.
          
          ?- areq([1,1,2,2],[2,1,2,1]).
          true.
          

          【讨论】:

            猜你喜欢
            • 2012-11-10
            • 2011-12-11
            • 2011-06-10
            • 2018-11-19
            • 1970-01-01
            • 2016-12-07
            • 1970-01-01
            • 2018-12-08
            • 1970-01-01
            相关资源
            最近更新 更多