web-dev-qa-db-ja.com

リスト内の重複を削除する(Prolog)

私はPrologにまったく慣れておらず、いくつかの演習を試しています。それらの1つは次のとおりです。

任意のリストを入力として受け取り、入力リストの各要素が1回だけ現れるリストを返す述語set(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が複数の同じ解を与えることです。入力リストの繰り返しが多いほど、より多くのソリューションが得られます。私は何が間違っているのですか?前もって感謝します。

15
evgeniuz

Prologのバックトラックにより、複数のソリューションが得られます。技術的には、提供される各ソリューションは正しいため、生成されています。ソリューションを1つだけ生成する場合は、ある時点でバックトラックを停止する必要があります。これは、Prolog cut が使用される目的です。あなたはそれを読むことがこの問題であなたを助けることに気付くかもしれません。

更新:そうです。最初の変数が2番目の変数の複数の位置にある場合、member()述語はいくつかの異なる方法でtrueとして評価されます。

GNU Prologの組み込みのmymember()述語と競合しないように、この述語には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]).は3つの異なる方法でtrueとして評価されます。

_| ?- mymember(1, [1, 1, 1]).

true ? a

true

true

no
_

答えを1つだけにしたい場合は、カットを使用する必要があります。 mymember()の最初の定義を次のように変更します。

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

あなたの問題を解決します。

さらに、必要に応じて、not()述語を自分で定義することにより、notamember()を完全に回避できます。選択はあなた次第です。

10
Tim

あなたは正しい方向に進んでいます...純粋にとどまります---それは簡単です!

A U B U Cのプロローグユニオン で実装されているように、具体化された等式述語=/3およびdif/3if_/3と組み合わせて使用​​します。

=(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年4月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は、そのmonotoneバリアントです。

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/2set/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] 
 ...% 普遍的に終了しません
7
repeat

より簡単な(そしておそらくより速い)解決策は、O(n log n)の重複を削除するライブラリ述語sort/2を使用することです。 YapプロローグとSWIPLで間違いなく動作します

6
sumx

これを行うためのより良い方法は次のようになると思います。

_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]
_
4
Ludwig

この古いスレッドに私の答えを追加します:

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

このソリューションの唯一の長所は、この演習が 元のテキスト)に表示される時点までに導入された述語のみを使用することです。

1
kjo

これはカットなしで機能しますが、より多くの行と別の引数が必要です。 3行目で[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,[]).
0
Michele Mendel
/* 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).
0

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