Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
817 views
in Technique[技术] by (71.8m points)

prolog - Finding Unique Items in a List

I'm trying to write a rule which decides whether an item X occurs exactly one in a list L.

unique(X, [X|T]):- !, + member(X, T).
unique(X, [_|T]):- unique(X, T).

The rule works for determining whether a value is unique within a list or nor, but when I try to get unique values within a list using unique(X, [1,2,3,1,3,2,5,4,3,8]). it returns just false. What I expected is this (like member(X, list).:

X = 5 ;
X = 4 ;
X = 8 ;

I'm a complete beginner and I don't know what I am doing wrong.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You have been using cut and an unsafe form of negation. Both have to be used with extreme care. An immediate fix would be to guard your program against uses it is not designed for:

unique(X, Xs) :-
   iwhen(ground(X+Xs), your_unique(X, Xs)).

This uses iwhen/2 which is similar to when/2 except that it does not delay:

:- meta_predicate iwhen(+, 0).

iwhen(Cond, G_0) :-
   when(Cond, ( Called = true, G_0 ) ),
   ( var(Called) -> throw(error(instantiation_error,_)) ; true ).

Above works for systems providing when/2. Below is for any ISO conforming system:

iwhen(Cond, G_0) :-
   (  when_condition(Cond)
   -> ( Cond -> G_0 ; throw(error(instantiation_error,_)) )
   ;  throw(error(domain_error(when_condition, Cond),_))
   ).

when_condition(C) :-
   var(C),
   !,
   throw(error(instantiation_error,_)).
when_condition(ground(_)).
when_condition(nonvar(_)).
when_condition(?=(_, _)).
when_condition(( A, B )) :-
   when_condition(A),
   when_condition(B).
when_condition(( A ; B )) :-
   when_condition(A),
   when_condition(B).

On the other hand, it gets quite frustrating receiving instantiation errors all the time instead of real answers. So, let's make your program really pure!

Your second rule

unique(X, [_|Es]) :-
   unique(X, Es).

reads declaratively, right-to-left (that :- is an )

Provided X is a unique element of the list Es, then X is a unique element of the list [_|Es].

in other words: Whenever I know that X is unique in Es, it will be also unique with any further element in Es. That conclusion is not true, consider to extend the list by X! You need some extra condition. Also, your first rule needs to be reformulated. This uses non_member/2:

unique(X, [X|Es]) :-
   non_member(X, Es).
unique(X, [E|Es]) :-
   dif(X, E),
   unique(X, Es).

And here is another way using tfilter/3:

unique(X, Es) :-
   tfilter(=(X), Es, [_]).

The most efficient is probably the following which uses if_/3 of library(reif):

unique(X, [E|Es]) :-
   if_(X = E, non_member(E, Es), unique(X, Es) ).

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...