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
的实现已被弃用


我建议使用所示的较新实现。

在这个答案中,我们比较了三种不同的列表成员资格谓词:

  • member/2
    ,一个内置谓词,在SWI Prolog中实现
  • 成员d/2
    ,由OP定义:

    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@false
memberd(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:As
true;false
这只是性能问题。还有其他方法可以解决这个问题。