使用Prolog中的cut来定义once_member / 2函数

时间:2011-11-26 23:36:44

标签: prolog prolog-cut

免责声明:这是我自己的非正式和非评估课程。我自己尝试过,失败了,现在正在寻找一些指导。

我正在尝试实现一个版本的member / 2函数,该函数只返回一次列表的成员。

例如:

| ?- member(X, [1,2,3,1]).
X = 1 ? ;
X = 2 ? ;
X = 3 ? ;
X = 1 ? ;

我希望它只能打印出最多一次的每个数字。

| ?- once_member(X, [1,2,3,1]).
X = 1 ? ;
X = 2 ? ;
X = 3 ? ;
no

我们被告知用剪切'!'来做这件事操作员,但我查看了我的课程笔记和更多的在线,但仍然不能让它点击我的脑袋!

到目前为止,我已成功获得:

once_member(E, [E | L]) :- !.
once_member(E, [_, L]) :-
    once_member(E, L).

返回1,然后没有别的,我觉得我的切割位置错误并且阻止了每次可能的匹配的回溯,但我真的不确定下一步该去哪里。

我查看了我的课程笔记以及http://www.cs.ubbcluj.ro/~csatol/log_funk/prolog/slides/5-cuts.pdfProgramming in Prolog (Google Books)

关于如何在逻辑上应用剪辑的指导将是最有用的,但答案可能有助于我自己解决这个问题。

我们也被告知要做另一种使用'\ +'否定失败的方法,但希望这一切可能更简单,一旦切割已经为我提供了支持?

4 个答案:

答案 0 :(得分:2)

删除多余的答案保持纯洁!

我们根据if_/3 and (=)/3定义memberd/2

memberd(X, [E|Es]) :-
   if_(X = E, true, memberd(X, Es)).

特别是对于元谓词,有时候不同的参数顺序会派上用场:

list_memberd(Es, X) :-
   memberd(X, Es).

示例查询:

?- memberd(X, [1,2,3,1]).
X = 1 ;
X = 2 ;
X = 3 ;
false.

答案 1 :(得分:1)

切割的解决方案......首先听起来很麻烦。

假设第一个参数将被实例化,解决方案是微不足道的:

once_member(X,L):-
    member(X,L),!.

但如果第一个arg未实例化,则不会出现您想要的行为 如果我们知道列表元素的域(例如1到42之间的数字),我们可以实例化第一个参数:

once_member(X,L):-
    between(1,42,X),
    member_(X,L).

member_(X,L):-
    member(X,L),!.

但这是低效率的

此时,我开始相信只用切割就不可能了(假设我们不使用+或list_to_set / 2
等一下! <在这里插入想法表情符号>

如果我们可以实现一个谓词(如list_to_set/2的swi-prolog),它将获取一个列表并生成一个列表,其中删除了所有重复元素,我们可以简单地使用普通成员/ 2并且不要得到重复的结果。尝试一下,我想你可以自己写一下。

--------掠夺者------------

    one_member(X,L):-
        list_to_set(L,S),
        member(X,S).

    list_to_set([],[]).
    list_to_set([H|T],[H|S]):-
        remove_all(H,T,TT),
        list_to_set(TT,S).

%remove_all(X,L,S): S is L if we remove all instances of X
    remove_all(_,[],[]).
    remove_all(X,[X|T],TT):-
        remove_all(X,T,TT),!.
    remove_all(X,[H|T],[H|TT]):-
        remove_all(X,T,TT).

如你所见,我们必须在remove_all / 3中使用cut,否则第三个子句可以由remove_all(X,[X|_],_)匹配,因为我们没有指定H与X不同。我相信没有的解是不重要的。

不过,没有的解决方案可以被描述为比切割解决方案更具声明性;我们使用的剪切通常称为红色剪切,因为它改变了程序的行为。还有其他问题;请注意,即使剪切,remove_all(1,[1,2],[1,2])也会成功。

另一方面,检查两次条件效率不高。因此,最佳的方法是使用if-then-else结构(但我假设您不允许使用它;它的实现可以通过剪切来完成)。

另一方面,还有另一个更简单的实现:不仅要检查X是否是列表的成员,还要检查先前是否遇到过它;所以你需要一个累加器:

-------------掠夺者--------------------

once_member(X,L):-
    once_member(X,L,[]).
once_member(X,[X|_T],A):-
    \+once_member(X,A).
once_member(X,[H|T],A):-
    once_member(X,T,[H|A]).

答案 2 :(得分:1)

once_member(X, Xs) :-
   sort(Xs, Ys),
   member(X, Ys).

与发布的几乎所有其他解决方案一样,这有一些异常现象。

?- X = 1, once_member(X, [A,B]).
X = A, A = 1 ;
X = B, B = 1.

?- X = 1, once_member(X, [A,A]).
X = A, A = 1.

答案 3 :(得分:-1)

这是一种使用 once_member / 2 定义中的剪切以及经典成员/ 2 谓词的方法:

once_member(X,[H|T]) :-
    member(H,T),
    !,
    once_member(X,T).
once_member(H,[H|_]).
once_member(X,[_|T]) :-
    once_member(X,T).

应用于上面的例子:

?- once_member(X,[1,2,3,1]).

X = 2 ;

X = 3 ;

X = 1 ;
no

注意:尽管出现了奇怪的三个子句定义,但 once_member / 2 是最后调用/尾部递归优化,因为在此之前放置了切口它的第一次自我调用。