5

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.

false
  • 10,264
  • 13
  • 101
  • 209
papafe
  • 2,959
  • 4
  • 41
  • 72

3 Answers3

6

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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    Your solution seems nice but I'm searching for a solution that uses only the predicates itself or basic predicates such as member, append, length, and obviously comparisons. – papafe Oct 04 '12 at 22:08
  • 2
    @markusian then you're out of luck. Counting how many times a predicate succeeds is not logic, it is above logic - i.e. it is not about proving something but about the workings of the mechanism to prove something. At the very least you have to use `assert`/`retract`, or `flag` etc. `findall` is just easier to use. – Will Ness Oct 05 '12 at 10:00
  • Thanks for the explanation Will! So, if counting how many times a predicates succeeds is not logic, I should think about another way to do so. – papafe Oct 05 '12 at 12:01
  • 1
    @markusian and that's the way of using `flag`, or `assert`/`retract`, or `findall`, or `nb_setarg`. All legitimate parts of a programming language, Prolog. http://www.swi-prolog.org/pldoc/man?predicate=nb_setarg%2f3 has sample `succeeds_n_times` predicate. – Will Ness Oct 05 '12 at 12:38
  • 1
    I just discovered that my problem was simpler than I thought because the predicate more should answer is if at least two facts li(Let,X), li(Let,Y), with X not equal to Y. I resolved it simply with `more(L):- li(L,X), li(L,Y), X \= Y, !.` – papafe Oct 09 '12 at 14:17
  • @markusian if that's the case, then yes. `more(d)` will fail then. – Will Ness Oct 09 '12 at 15:08
5
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.)

false
  • 10,264
  • 13
  • 101
  • 209
2

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...

Community
  • 1
  • 1
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • hmm, calling `findall(X, pred(X), [_,_|_])` may be read as showing the intent to cut down the number of actual calls to `pred` to no more than two ... but this could only be done at the level of implementation, right? Or may be it merits another name for the predicate altogether, like `findsome`. :) `findall` says "find *all*" after all. – Will Ness Oct 04 '12 at 19:53