8

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

我正在尝试实现 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.pdf和Prolog 中的编程(Google 图书)

关于如何在逻辑上应用削减的指导将是最有用的,但答案可能会帮助我自己弄清楚这一点。

我们还被告知要做另一种方法,该方法使用'\+' 否定失败,但希望一旦 cut 对我来说这可能会更简单?

4

4 回答 4

2

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

我们memberd/2基于if_/3(=)/3定义:

成员(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.
于 2015-04-03T13:27:24.683 回答
1

使用 cut... 的解决方案一开始听起来很麻烦。

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

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),!.

但这是非常低效的

此时,我开始相信,不能只用一个cut(假设我们不使用+或list_to_set/2
哦等等!<在此处插入idea emoticon>

如果我们可以实现一个谓词(如swi-prolog 的list_to_set/2),它将接受一个列表并生成一个列表,其中所有重复的元素都被删除,我们可以简单地使用普通的 member/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 不同。我相信 not 的解决方案是微不足道的。

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

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

另一方面,还有另一个更简单的 not 实现:您不仅应该检查 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]).
于 2011-11-27T00:35:50.943 回答
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.
于 2011-11-28T08:34:24.773 回答
-1

这是一种在once_member/2的定义中与经典member/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

注意:尽管出现了奇怪的三个子句定义,但由于在其第一次自调用之前放置了剪切,因此 one_member/2符合最后调用/尾递归优化的条件。

于 2011-11-27T14:51:13.977 回答