List “memberd/2”的更多决定论?
许多系统提供了List “memberd/2”的更多决定论?,list,prolog,List,Prolog,许多系统提供了member/2的纯粹而高效的实现。特别是,没有任何选择点可供选择: ?- member(b,[a,b]). true. 然而,member/2的简单实现会产生: ?- member(b,[a,b]). true ; false. 从声明的观点来看,这当然是正确的,但效率较低 另一方面,member/2存在一些技术问题。它允许冗余解决方案,如: ?- member(a,[a,a]). true ; true. memberd/2使用和(=)/3解决此问题 memberd(E,
member/2
的纯粹而高效的实现。特别是,没有任何选择点可供选择:
?- member(b,[a,b]).
true.
然而,member/2
的简单实现会产生:
?- member(b,[a,b]).
true ;
false.
从声明的观点来看,这当然是正确的,但效率较低
另一方面,member/2
存在一些技术问题。它允许冗余解决方案,如:
?- member(a,[a,a]).
true ;
true.
memberd/2
使用和(=)/3
解决此问题
memberd(E, [X|Xs]) :-
if_(E = X, true, memberd(E, Xs)).
?- memberd(a,[a,a]).
true.
不幸的是,这个定义让选择点再次打开——产生;false(错误)
(“剩余选择点”),在成员不符合以下条件的情况下:
?- memberd(X,[a,b]).
X = a ;
X = b ;
false. % BAD - to be avoided!
?- member(X,[a,b]).
X = a ;
X = b.
所以我的问题是:
memberd/2
是否有一个定义可以避免上述选择点?首先,为了清晰起见,我们将memberd
重命名为memberd\u old
然后,我们实现了memberd_new/2
,它使用滞后和第一个参数索引来防止在列表末尾创建无用的choicepoint
memberd_new(E,[X|Xs]) :-
memberd_new_aux(Xs,X,E).
% auxiliary predicate to enable first argument indexing
memberd_new_aux([],E,E).
memberd_new_aux([X1|Xs],X0,E) :-
if_(E=X0, true, memberd_new_aux(Xs,X1,E)).
让我们比较一下member/2
(SWI-Prolog内置谓词)、memberd\u-old/2
和memberd\u-new/2
首先,一个地面查询:
?- member(a,[a,a]).
true ;
true. % BAD!
?- memberd_old(a,[a,a]).
true.
?- memberd_new(a,[a,a]).
true.
?- member(a,[a,b]).
true ; % BAD!
false.
?- memberd_old(a,[a,b]).
true.
?- memberd_new(a,[a,b]).
true.
接下来,另一个地面查询:
?- member(a,[a,a]).
true ;
true. % BAD!
?- memberd_old(a,[a,a]).
true.
?- memberd_new(a,[a,a]).
true.
?- member(a,[a,b]).
true ; % BAD!
false.
?- memberd_old(a,[a,b]).
true.
?- memberd_new(a,[a,b]).
true.
现在,一个具有多个不同解决方案的查询:
?- member(X,[a,b]).
X = a ;
X = b.
?- memberd_old(X,[a,b]).
X = a ;
X = b ; % BAD!
false.
?- memberd_new(X,[a,b]).
X = a ;
X = b.
?- member(X,[a,b]).
X = a ; X = b.
?- memberd(X,[a,b]).
X = a ; X = b ; false. % BAD
?- memberd_new(X,[a,b]).
X = a ; X = b.
编辑
此处介绍的memberd_new/2
的实现已被弃用
我建议使用所示的较新实现。在这个答案中,我们比较了三种不同的列表成员资格谓词:
,一个内置谓词,在SWI Prolog中实现member/2
,由OP定义:成员d/2
memberd(E,[X|Xs]) :- if_(E=X, true, memberd(E,Xs)).
,一个建议的替代方案,定义如下:memberd_new/2
memberd_new(E,[X|Xs]) :- ( Xs \= [_|_] -> E=X ; if_(E=X, true, memberd_new(E,Xs)) ).
我们走吧 首先,一些基本问题:
?- member(b,[a,b]).
true.
?- memberd(b,[a,b]).
true.
?- memberd_new(b,[a,b]).
true.
?- member(a,[a,a]).
true ; true. % BAD
?- memberd(a,[a,a]).
true.
?- memberd_new(a,[a,a]).
true.
?- member(a,[a,b]).
true ; false. % BAD
?- memberd(a,[a,b]).
true.
?- memberd_new(a,[a,b]).
true.
接下来,一些查询具有多个不同的解决方案:
?- member(X,[a,b]).
X = a ;
X = b.
?- memberd_old(X,[a,b]).
X = a ;
X = b ; % BAD!
false.
?- memberd_new(X,[a,b]).
X = a ;
X = b.
?- member(X,[a,b]).
X = a ; X = b.
?- memberd(X,[a,b]).
X = a ; X = b ; false. % BAD
?- memberd_new(X,[a,b]).
X = a ; X = b.
接下来,在一篇评论中建议了一个测试用例
它打破了前面介绍的memberd_new/2
版本
?- member(a,[a|nonlist]).
true.
?- memberd(a,[a|nonlist]).
true.
?- memberd_new(a,[a|nonlist]).
true. % IMPROVED
上述测试用例的变化:
?- member(X,[a|nonlist]).
X = a.
?- memberd(X,[a|nonlist]).
X = a ; false. % BAD
?- memberd_new(X,[a|nonlist]).
X = a. % IMPROVED
最后,一些非终止查询:
?- member(1,Xs).
Xs = [1|_A]
; Xs = [_A,1|_B]
; Xs = [_A,_B,1|_C]
; Xs = [_A,_B,_C,1|_D]
...
?- memberd(1,Xs).
Xs = [1|_A]
; Xs = [_A,1|_B], dif(_A,1)
; Xs = [_A,_B,1|_C], dif(_A,1), dif(_B,1)
; Xs = [_A,_B,_C,1|_D], dif(_A,1), dif(_B,1), dif(_C,1)
...
?- memberd_new(1,Xs).
Xs = [1|_A]
; Xs = [_A,1|_B], dif(_A,1)
; Xs = [_A,_B,1|_C], dif(_A,1), dif(_B,1)
; Xs = [_A,_B,_C,1|_D], dif(_A,1), dif(_B,1), dif(_C,1)
...
还有更多。。。假设我们基于
memberD\u t/3
实现memberD/2
:
memberD(X,Xs) :- memberd_t(X,Xs,true).
在上述查询中,memberd/2
和memberd/2
给出了相同的答案,并在相同的实例中留下了多余的选择点
让我们挖得更深一点!考虑以下问题:使用代码> >成员/ 3 /代码>角情况:
?- memberd_t(a,[a|nonlist],T).
T = true. % OK
?- memberd_t(b,[a|nonlist],T).
false. % missing: `T = false`
?- memberd_t(X,[a|nonlist],T).
T = true, X = a
; false. % missing: `T = false, dif(X,a)`
让我们检查一下,使用memberd\u new\u t/3
是否可以减少无用的选项点数
?- memberd_t(X,[a,b],true).
X = a ; X = b ; false. % suboptimal
?- memberd_new_t(X,[a,b],true).
X = a ; X = b. % BETTER
?- memberd_t(X,[a|nonlist],true).
X = a ; false. % suboptimal
?- memberd_new_t(X,[a|nonlist],true).
X = a. % BETTER
它起作用了!最后,考虑最一般的查询:
?- memberd_t(X,Xs,T).
T=false, Xs = []
; T=true , Xs = [X |_A]
; T=false, Xs = [_A ], dif(X,_A)
; T=true , Xs = [_A, X |_B], dif(X,_A)
; T=false, Xs = [_A,_B ], dif(X,_A), dif(X,_B)
; T=true , Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, Xs = [_A,_B,_C ], dif(X,_A), dif(X,_B), dif(X,_C)
...
?- memberd_new_t(X,Xs,T).
T=false, freeze(Xs,Xs\=[_|_])
; T=true , Xs = [ X |_A]
; T=false, freeze(_B,_B\=[_|_]), Xs = [_A |_B], dif(X,_A)
; T=true , Xs = [_A, X |_B], dif(X,_A)
; T=false, freeze(_C,_C\=[_|_]), Xs = [_A,_B |_C], dif(X,_A), dif(X,_B)
; T=true , Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, freeze(_D,_D\=[_|_]), Xs = [_A,_B,_C|_D], dif(X,_A), dif(X,_B), dif(X,_C)
...
对于这些极端情况,memberd\u new\u t/3
更像memberd/3
,而不是memberd\u t/3
:
memberD(X,Xs) :- memberd_t(X,Xs,true).
?- memberd(X,Xs).
Xs = [ X |_A]
; Xs = [_A, X |_B], dif(X,_A)
; Xs = [_A,_B, X |_C], dif(X,_A), dif(X,_B)
; Xs = [_A,_B,_C, X |_D], dif(X,_A), dif(X,_B), dif(X,_C)
; Xs = [_A,_B,_C,_D, X|_E], dif(X,_A), dif(X,_B), dif(X,_C), dif(X,_D)
...
~与
member/2
或memberd/2
相比,这是一个限制。不确定这是否是一个好主意。memberd(a[a | nonlist])
成功,但在您的version@falsememberd(a[a | nonlist])
成功背后的基本原理是什么?第二个参数甚至不是一个列表?@Boris:它是一个具有性能优势的泛化。想想成员d(a[a,b,c,d])
,它不需要访问整个列表就可以立即确定。相反地,如果你总是强加不允许泛化,那么你的终止属性就会弱得多(想想append/3
)的第二个参数。@Boris:而且,类似的参数适用于部分列表的枚举答案,repeat的aproach生成的答案是原来的两倍。一种单元测试会很有用。类似于“嘿,伙计们,让它成功吧”。@dlask:只有顶层向你展示了我们想要成功的东西。可以使用setup\u call\u cleanup/3
,但这会让人更加困惑。我的意思是,有许多不同的参数和响应组合,让所有这些检查自动化会很舒服。顺便问一下,是否有可能通过编程来检测选择点?@Boris:声明性阅读始终只是一种基于基本术语的阅读。因此,冗余解决方案完全没有任何意义。如果你有几个这样的目标,这可能会变得更加明显。比如:member(a,[a,a]),member(a,[a,a])
这三个额外的答案没有任何声明性的含义——在这种特殊情况下,使用一次/1
并不是最坏的主意。注意,once/1
不能用于每个地面目标,因为潜在的不一致性-想想p:-冻结(u,false)。
和查询q
@Boris:Astrue;false
这只是性能问题。还有其他方法可以解决这个问题。