Pure Prolog程序以清洁的方式区分术语的平等和不平等,导致执行效率低下;即使所有相关条款都是基础的。
有关SO的最新示例是this answer。在此定义中,所有答案和所有失败都是正确的。考虑:
?- Es = [E1,E2], occurrences(E, Es, Fs).
Es = Fs, Fs = [E, E],
E1 = E2, E2 = E ;
Es = [E, E2],
E1 = E,
Fs = [E],
dif(E, E2) ;
Es = [E1, E],
E2 = E,
Fs = [E],
dif(E, E1) ;
Es = [E1, E2],
Fs = [],
dif(E, E1),
dif(E, E2).
虽然程序从声明的角度看是完美无缺的,但它在B,SICStus,SWI,YAP等当前系统上的直接执行效率是不必要的。对于以下目标,对于列表中的每个元素,选择点保持打开。
?- occurrences(a,[a,a,a,a,a],M). M = [a, a, a, a, a] ; false.
这可以通过使用足够大的a
列表来观察,如下所示。您可能需要调整I
,以便仍然可以表示列表;在SWI,这意味着
1mo I
必须小到足以防止全局堆栈的资源错误,如下所示:
?- 24=I,N is 2^I,length(L,N), maplist(=(a),L). ERROR: Out of global stack
2do I
必须足够大才能引发本地堆栈的资源错误:
?- 22=I,N is 2^I,length(L,N), maplist(=(a),L), ( Length=ok ; occurrences(a,L,M) ). I = 22, N = 4194304, L = [a, a, a, a, a, a, a, a, a|...], Length = ok ; ERROR: Out of local stack
为了克服这个问题并仍然保留好的声明性属性,需要一些比较谓词。
这是一个可能的定义:
equality_reified(X, X, true). equality_reified(X, Y, false) :- dif(X, Y).
编辑:也许应该颠倒参数顺序,类似于ISO内置compare/3
(仅链接到草稿的链接)。
它的有效实现将首先处理快速确定的案例:
equality_reified(X, Y, R) :- X == Y, !, R = true. equality_reified(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different equality_reified(X, Y, R) :- X \= Y, !, R = false. % semantically different equality_reified(X, X, true). equality_reified(X, Y, false) :- dif(X, Y).
编辑:我不清楚X \= Y
是否在存在约束的情况下是合适的防护。没有约束,?=(X, Y)
或X \= Y
是相同的。
正如@ user1638891所建议的,这是一个如何使用这种原语的例子。垫子的原始代码是:
occurrences_mats(_, [], []).
occurrences_mats(X, [X|Ls], [X|Rest]) :-
occurrences_mats(X, Ls, Rest).
occurrences_mats(X, [L|Ls], Rest) :-
dif(X, L),
occurrences_mats(X, Ls, Rest).
可以将其重写为:
occurrences(_, [], []).
occurrences(E, [X|Xs], Ys0) :-
reified_equality(Bool, E, X),
( Bool == true -> Ys0 = [X|Ys] ; Ys0 = Ys ),
% ( Bool = true, Ys0 = [X|Ys] ; Bool = true, Ys0 = Ys ),
occurrences(E, Xs, Ys).
reified_equality(R, X, Y) :- X == Y, !, R = true.
reified_equality(R, X, Y) :- ?=(X, Y), !, R = false.
reified_equality(true, X, X).
reified_equality(false, X, Y) :-
dif(X, Y).
请注意,SWI的第二个参数索引仅在您输入occurrences(_,[],_)
之类的查询后激活。此外,SWI需要本质上非单调的if-then-else,因为它不会在(;)/2
上进行索引 - 析取。 SICStus这样做,但只有第一个参数索引。所以它留下一(1)个选择点打开(最后有[]
)。
答案 0 :(得分:9)
一方面,名称应该更具说明性,如equality_truth/2
。
答案 1 :(得分:7)
以下代码基于if_/3
和(=)/3
(a.k.a。equal_truth/3
),由Prolog union for A U B U C中的@false实现:
=(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).
if_(C_1, Then_0, Else_0) :-
call(C_1, Truth),
functor(Truth,_,0), % safety check
( Truth == true -> Then_0 ; Truth == false, Else_0 ).
与occurrences/3
相比,辅助occurrences_aux/3
使用不同的参数顺序,将列表Es
作为第一个参数传递,这可以启用第一个参数索引:
occurrences_aux([], _, []).
occurrences_aux([X|Xs], E, Ys0) :-
if_(E = X, Ys0 = [X|Ys], Ys0 = Ys),
occurrences_aux(Xs, E, Ys).
正如@migfilg所指出的,目标Fs=[1,2], occurrences_aux(Es,E,Fs)
应该失败,因为它在逻辑上是错误的:
occurrences_aux(_,E,Fs)
指出Fs
中的所有元素都等于E
。
但是,occurrences_aux/3
本身并不会在这种情况下终止。
我们可以使用辅助谓词allEqual_to__lazy/2
来改善终止行为:
allEqual_to__lazy(Xs,E) :-
freeze(Xs, allEqual_to__lazy_aux(Xs,E)).
allEqual_to__lazy_aux([],_).
allEqual_to__lazy_aux([E|Es],E) :-
allEqual_to__lazy(Es,E).
在所有辅助谓词到位的情况下,让我们定义occurrences/3
:
occurrences(E,Es,Fs) :-
allEqual_to__lazy(Fs,E), % enforce redundant equality constraint lazily
occurrences_aux(Es,E,Fs). % flip args to enable first argument indexing
我们有一些疑问:
?- occurrences(E,Es,Fs). % first, the most general query
Es = Fs, Fs = [] ;
Es = Fs, Fs = [E] ;
Es = Fs, Fs = [E,E] ;
Es = Fs, Fs = [E,E,E] ;
Es = Fs, Fs = [E,E,E,E] ... % will never terminate universally, but ...
% that's ok: solution set size is infinite
?- Fs = [1,2], occurrences(E,Es,Fs).
false. % terminates thanks to allEqual_to__lazy/2
?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1], E=1 ;
Fs = [2,2], E=2 ;
Fs = [3,3], E=3 ;
Fs = [], dif(E,1), dif(E,2), dif(E,3).
?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1]. % succeeds deterministically
?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E, E], Fs = [E,E], E1=E , E2=E ;
Es = [E, E2], Fs = [E], E1=E , dif(E2,E) ;
Es = [E1, E], Fs = [E], dif(E1,E), E2=E ;
Es = [E1,E2], Fs = [], dif(E1,E), dif(E2,E).
?- occurrences(1,[E1,1,2,1,E2],Fs).
E1=1 , E2=1 , Fs = [1,1,1,1] ;
E1=1 , dif(E2,1), Fs = [1,1,1] ;
dif(E1,1), E2=1 , Fs = [1,1,1] ;
dif(E1,1), dif(E2,1), Fs = [1,1].
如果occurrences/3
通用在某些情况下终止,还有一些测试用于测试:
?- occurrences(1,L,[1,2]).
false.
?- L = [_|_],occurrences(1,L,[1,2]).
false.
?- L = [X|X],occurrences(1,L,[1,2]).
false.
?- L = [L|L],occurrences(1,L,[1,2]).
false.
答案 2 :(得分:5)
最好使用相同的参数(=)/3
来调用此谓词。通过这种方式,if_/3
之类的条件现在更具可读性。并使用后缀_t
代替_truth
:
memberd_t(_X, [], false).
memberd_t(X, [Y|Ys], Truth) :-
if_( X = Y, Truth=true, memberd_t(X, Ys, Truth) ).
过去是:
memberd_truth(_X, [], false).
memberd_truth(X, [Y|Ys], Truth) :-
if_( equal_truth(X, Y), Truth=true, memberd_truth(X, Ys, Truth) ).
答案 3 :(得分:4)
更新:这个答案已被我的4月18日取代。由于下面的评论,我不建议将其删除。
我之前的回答是错误的。以下针对问题中的测试用例运行,并且实现具有避免多余选择点的期望特征。我假设顶级谓词模式是?,+,?虽然其他模式很容易实现。
该程序共有4个子句:访问第二个参数中的列表,对于每个成员,有两种可能性:它与顶级谓词的第一个参数统一或与之不同,在这种情况下{{{ 1}}约束适用:
dif
使用YAP运行样本:
occurrences(X, L, Os) :- occs(L, X, Os).
occs([],_,[]).
occs([X|R], X, [X|ROs]) :- occs(R, X, ROs).
occs([X|R], Y, ROs) :- dif(Y, X), occs(R, Y, ROs).
答案 4 :(得分:3)
这是occurrences/3
的更短逻辑纯实现。
我们在meta-predicate tfilter/3
上构建它
具体术语等式谓词(=)/3
,以及协程allEqual_to__lazy/2
(在此问题的my previous answer中定义):
occurrences(E,Xs,Es) :-
allEqual_to__lazy(Es,E),
tfilter(=(E),Xs,Es).
完成!为了简化比较,我们重新运行与之前答案中使用的完全相同的查询:
?- Fs = [1,2], occurrences(E,Es,Fs).
false.
?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1], E=1 ;
Fs = [2,2], E=2 ;
Fs = [3,3], E=3 ;
Fs = [], dif(E,1), dif(E,2), dif(E,3).
?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1].
?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E, E ], Fs = [E,E], E1=E , E2=E ;
Es = [E, E2], Fs = [E], E1=E , dif(E2,E) ;
Es = [E1,E ], Fs = [E], dif(E1,E), E2=E ;
Es = [E1,E2], Fs = [], dif(E1,E), dif(E2,E).
?- occurrences(1,[E1,1,2,1,E2],Fs).
E1=1 , E2=1 , Fs = [1,1,1,1] ;
E1=1 , dif(E2,1), Fs = [1,1,1] ;
dif(E1,1), E2=1 , Fs = [1,1,1] ;
dif(E1,1), dif(E2,1), Fs = [1,1].
?- occurrences(1,L,[1,2]).
false.
?- L = [_|_],occurrences(1,L,[1,2]).
false.
?- L = [X|X],occurrences(1,L,[1,2]).
false.
?- L = [L|L],occurrences(1,L,[1,2]).
false.
最后,最常见的查询:
?- occurrences(E,Es,Fs).
Es = Fs, Fs = [] ;
Es = Fs, Fs = [E] ;
Es = Fs, Fs = [E,E] ;
Es = Fs, Fs = [E,E,E] % ... and so on ad infinitum ...
我们得到了相同的答案。
答案 5 :(得分:2)
以下occurrences/3
的实现基于我以前的答案,即从第一个参数的子句索引机制中获利,以避免一些选择点,并解决所有引发的问题。
此外,它应对到目前为止所有提交的实现中的问题,包括问题中提到的问题,即当查询具有2个第一个参数空闲时它们都进入无限循环而第3个参数具有不同的列表地面元素。当然,正确的行为是失败的。
使用比较谓词
我认为,为了避免未使用的选择点并保持良好程度的实现声明性,不需要像问题中提出的那样使用比较谓词,但我同意这可能是品味问题或倾斜度。
<强>实施强>
按此顺序考虑三个独占案例:如果第二个参数是地面,则递归访问;否则如果第三个参数是地,则检查然后再递归访问;否则会为第二个和第三个参数生成合适的列表。
occurrences(X, L, Os) :-
( nonvar(L) -> occs(L, X, Os) ;
( nonvar(Os) -> check(Os, X), glist(Os, X, L) ; v_occs(L, X, Os) ) ).
当列表不为空时,访问地面第二个参数有三种情况:如果它的头部和上面的X
都是基础且可统一的X
是在结果列表中出现的而且别无选择;否则有两种选择,X
与头部不同或与之统一:
occs([],_,[]).
occs([X|R], Y, ROs) :-
( X==Y -> ROs=[X|Rr] ; foccs(X, Y, ROs, Rr) ), occs(R, Y, Rr).
foccs(X, Y, ROs, ROs) :- dif(X, Y).
foccs(X, X, [X|ROs], ROs).
检查地面第3个参数包括确保其所有成员与X
统一。原则上,此检查可以由glist/3
执行,但这样可以避免使用未使用的选择点。
check([], _).
check([X|R], X) :- check(R, X).
使用免费的第二个参数访问第三个参数必须通过向生成的列表添加不同于X
的变量来终止。在每个递归步骤中,有两种选择:生成列表的当前头部是访问列表的当前头部,必须与X
一致,或者是与X
不同的自由变量。这是一个理论上的描述,因为实际上存在无限数量的解,并且当列表头是变量时将永远不会达到第3个子句。因此,下面的第三个条款被注释掉,以避免不可用的选择点。
glist([], X, L) :- gdlist(L,X).
glist([X|R], X, [X|Rr]) :- glist(R, X, Rr).
%% glist([X|R], Y, [Y|Rr]) :- dif(X, Y), glist([X|R], Y, Rr).
gdlist([], _).
gdlist([Y|R], X) :- dif(X, Y), gdlist(R, X).
最后,所有参数都是免费的情况是以类似于前一种情况的方式处理的,并且存在一些类似的问题,即某些解决方案模式没有在实践中生成:
v_occs([], _, []).
v_occs([X|R], X, [X|ROs]) :- v_occs(R, X, ROs).
%% v_occs([X|R], Y, ROs) :- dif(Y, X), v_occs(R, Y, ROs). % never used
样本测试
?- occurrences(1,[E1,1,2,1,E2],Fs).
Fs = [1,1],
dif(E1,1),
dif(E2,1) ? ;
E2 = 1,
Fs = [1,1,1],
dif(E1,1) ? ;
E1 = 1,
Fs = [1,1,1],
dif(E2,1) ? ;
E1 = E2 = 1,
Fs = [1,1,1,1] ? ;
no
?- occurrences(1,L,[1,2]).
no
?- occurrences(1,L,[1,E,1]).
E = 1,
L = [1,1,1] ? ;
E = 1,
L = [1,1,1,_A],
dif(1,_A) ? ;
E = 1,
L = [1,1,1,_A,_B],
dif(1,_A),
dif(1,_B) ? ;
...