union(A, B, C, U) :-
union(A, B, V),
union(C, V, U).
你对union/3的定义可以通过替换来改进
... not(element(X,L)), ...
通过
... maplist(dif(X),L), ...
或
... non_member(X, L), ....
non_member(_X, []).
non_member(X, [E|Es]) :-
dif(X, E),
non_member(X, Es).
这是一个差异显示的案例:
?- union([A],[B],[C,D]).
A = C,
B = D,
dif(C, D).
[A] 和 [B] 必须如何看起来才能使它们的并集包含 2 个元素?
答案是:它们一定是不同的。
您的原始版本在此查询中失败,但对于如下特殊实例却成功:
?- A = 1, B = 2, union([A],[B],[C,D]).
因此它成功了,但无法概括它。因此它不是一个纯粹的逻辑关系。
那么dif/2 一切都很好吗?不幸的是没有。 @TudorBerariu 有充分的理由去削减,因为它反映了我们对这种关系的一些意图。削减有效地反映了两个关键意图
只有在术语没有充分实例化时才会出现问题..
OP 的定义和上述定义的缺点是两者都过于笼统,可以通过 Arg2 中的重复元素观察到:
?- union([a,a],[a,a],Zs).
Zs = [a, a] ;
Zs = [a, a] ;
Zs = [a, a] ;
Zs = [a, a] ;
false.
事实上,我们得到 |Arg2||Arg1|-1 个冗余答案。因此,削减有一些很好的理由。
目前union/3 效率不高的另一个原因是,对于(预期的)基本情况,它留下了不必要的选择点。同样,@TudorBerariu 的解决方案没有这个问题:
?- union([a],[a],Zs).
Zs = [a] ; % <--- Prolog does not know that there is nothing left.
false.
消除冗余
许多多余答案的真正罪魁祸首是第一条规则。 element(a,[a,a])(俗称member/2)会成功两次。
union([X|Y],L,S) :- element(X,L), union(Y,L,S).
^^^^^^^^^^^^
这是一个改进的定义:
memberd(X, [X|_Ys]).
memberd(X, [Y|Ys]) :-
dif(X,Y), % new!
memberd(X, Ys).
递归规则,从右到左阅读,如下:
假设memberd(X, Ys) 对于某些X 和Ys 已经成立。鉴于此,并且我们有一个合适的Y,它不同于X。那么
我们可以得出结论,memberd(X, [Y|Ys]) 也是正确的。
所以这消除了多余的解决方案。但是我们的定义仍然不是很有效:它仍然要对每个元素访问 Arg2 两次,然后无法得出没有备选方案的结论。在任何情况下:拒绝切割将其移除。
通过具体化引入确定性。
比较memberd/2 和non_member/2 的定义。尽管它们描述了彼此的“相反”,但它们看起来非常相似:
non_member(_X, []).
non_member(X, [Y|Ys]) :-
dif(X,Y),
non_member(X, Ys).
memberd(X, [X|_Ys]).
memberd(X, [Y|Ys]) :-
dif(X,Y),
memberd(X, Ys).
递归规则是一样的!只有事实不同。让我们将它们合并到一个定义中——用一个额外的参数来判断我们是指memberd (true) 还是non_member (false):
memberd_t(_X, [], false).
memberd_t(X, [X|_Ys], true).
memberd_t(X, [Y|Ys], Truth) :-
dif(X, Y),
memberd_t(X, Ys, Truth).
现在,我们的定义变得更紧凑了:
unionp([], Ys, Ys).
unionp([X|Xs], Ys, Zs0) :-
if_( memberd_t(X, Ys), Zs0 = Zs, Zs0 = [X|Zs] ),
unionp(Xs, Ys, Zs).
memberd_t(_X, [], false). % see below
memberd_t(X, [Y|Ys], Truth) :-
if_( X = Y, Truth=true, memberd_t(X, Ys, Truth) ).
注意if_(If_1, Then_0, Else_0) 和 if-then-else 控制结构( If_0 -> Then_0 ; Else_0 ) 之间的区别。虽然 If_1 可能会成功多次并具有不同的真值(即,它可以同时为真和假),但控制结构使 If_0 仅成功一次,因为它只为真。
if_(If_1, Then_0, Else_0) :-
call(If_1, T),
( T == true -> call(Then_0)
; T == false -> call(Else_0)
; nonvar(T) -> throw(error(type_error(boolean,T),_))
; /* var(T) */ throw(error(instantiation_error,_))
).
=(X, Y, T) :-
( X == Y -> T = true
; X \= Y -> T = false
; T = true, X = Y
; T = false,
dif(X, Y) % ISO extension
% throw(error(instantiation_error,_)) % ISO strict
).
equal_t(X, Y, T) :-
=(X, Y, T).
为确保memberd_t/3 始终从第一个参数索引中受益,请使用以下定义(感谢@WillNess):
memberd_t(E, Xs, T) :-
i_memberd_t(Xs, E, T).
i_memberd_t([], _E, false).
i_memberd_t([X|Xs], E, T) :-
if_( X = E, T = true, i_memberd_t(Xs, E, T) ).