Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prolog - how to check if a predicate succeeds more than once

Tags:

prolog

I have a database of facts like this:

li(a,2).
li(b,3).
li(b,1).
li(c,2).
li(d,1).
li(d,1).

I need to write a predicate more(+Let) that succeeds if it exists more than one fact li(Let,_).

For example the queries more(b) and more(d) will succeed, but more(a) and more(c) will not. My idea was to check if li(Let,_) succeeds more than once, but I do not know how to do it.

like image 1000
papafe Avatar asked Oct 04 '12 17:10

papafe


3 Answers

Try findall/3:

findall(X, li(d,X), L), length(L,N), N>1.

Abstracting the d out and making a predicate is trivial. Right? :)


If you don't want to use any of the predicates like findall, you can change the representation of your knowledge - bring it down one level, so to speak:

my_knowledge(li, [a-2,b-3,b-1,c-2,d-1,d-1]).

and then you can use SWI Prolog's predicate select/3 to handle it:

select_knowledge(kn, key, R):-
  my_knowledge(kn,L),
  select_key(L,key,R).

select_key(L,K,R):-
  select(K-X,L,L1) -> R=[X|R1], select_key(L1,K,R1)
  ; R = [].

You can rewrite the last predicate as basic recursion over lists, and then tweak it to stop after getting first N results.

like image 169
Will Ness Avatar answered Oct 21 '22 23:10

Will Ness


more_than_once(Goal) :-
   \+ \+ call_nth(Goal,2).

With call_nth/2 as defined in this answer.

The big advantage of this solution compared to the other solutions proposed is that it will succeed rapidly even if there is a very large sequence of answers. In fact, it will even succeed for an infinite sequence of answers:

?- more_than_once(repeat).
   true.
?- more_than_once(between(1,100000,_)).
   true.

(The implementation of call_nth/2 uses some non-standard, low-level built-ins of SWI. It is possible to avoid that, but with even more headache.)

like image 39
false Avatar answered Oct 21 '22 22:10

false


SWI-Prolog has library(aggregate).

:- [library(aggregate)].

more(Key) :- aggregate_all(count, li(Key, _), C), C > 1.

test:

?- more(b).
true.

?- more(a).
false.

It's not very easy to learn, but useful to handle such common tasks. If you have a very large code base, then findall (and aggregate as well, that uses findall inside) could be inefficient, building a list only to count its elements.

Then you could use a side effect based predicate: in this related answer you'll find such utility. For max efficiency, see the comments, where is explained how to use nb_setval/nb_getval...

like image 2
CapelliC Avatar answered Oct 21 '22 22:10

CapelliC