PROLOG:如果顺序不重要,确定列表中的元素是否相等

时间:2015-05-10 23:12:22

标签: prolog

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

我的第一次尝试是:

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

我会尝试在左边重建lest,最后交换列表;但这次失败了。

我对递归的感觉非常差,根本不知道如何改进它,尤其是Prolog。任何提示或建议都将在此时受到赞赏。

6 个答案:

答案 0 :(得分:9)

使用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

答案 1 :(得分:9)

作为一个起点,让@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]).
true.
?- equal_elementsB([1,2,3,2,3],[3,3,2,1,2,3]).
false.

如果我们希望第二个查询成功,我们可以通过使用元谓词以逻辑上纯粹的方式放宽定义 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]).
true.
?- equal_elementsC([1,2,3,2,3],[3,3,2,1,2,3]).
true.

编辑2015-05-17

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

?- equal_elementsB([],Xs), false.         % terminates universally
false.    
?- equal_elementsB([_],Xs), false.        % gives a single answer, but ...
%%% wait forever                          % ... does not terminate universally

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

?- equal_elementsB(Xs,[]), false.         % terminates universally
false.
?- equal_elementsB(Xs,[_]), false.        % terminates universally
false.

an answer given by @AmiTavory的启发,我们可以通过"锐化"来改进equal_elementsB/2的实施解决方案设置如下:

equal_elementsBB(Xs,Ys) :-
   same_length(Xs,Ys),
   equal_elementsB(Xs,Ys).

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

?- equal_elementsB([_],Xs), false.
%%% wait forever                          % does not terminate universally

?- equal_elementsBB([_],Xs), false.
false.                                    % terminates universally

请注意同样的"技巧"不适用于equal_elementsC/2, 因为解决方案集的大小是无限的(对于所有感兴趣但最简单的实例)。

答案 2 :(得分:6)

紧凑形式:

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

答案 3 :(得分:3)

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

  

对常规用途有效。

接受的答案是 O(最大值(| 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).

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

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

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

如果两个列表具有不同的摘要,则它们不能相等。如果两个列表具有相同的摘要,那么它们不相等的可能性很小,但仍需要检查。在这种情况下,我无耻地偷走了保罗·莫拉的优秀答案。

最终的代码是:

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

答案 4 :(得分:1)

一种可能性,受到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).

答案 5 :(得分:0)

使用 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.