34

I want to count the number of times a custom predicate is true. For example, I have the following code:

is_man(john).
is_man(alex).
?:-is_man(X).

X will return john, then if I press semicolon it will also return alex, then false.

I want to build something like:

count(is_man(X), Count).

And this to return

Count = 2

How can I do that?

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
Victor Blaga
  • 1,822
  • 4
  • 19
  • 28

3 Answers3

40

In SWI-Prolog:

aggregate_all(count, is_man(X), Count).
Kaarel
  • 10,554
  • 4
  • 56
  • 78
  • Is it possible to create a distinct count? like, if you have 2 times the same name,not to increase the counter – Shevliaskovic Nov 01 '13 at 09:24
  • 2
    @Shevliaskovic: The short answer is yes, it is possible. `aggregate_all/3` above uses `findall/3` and counts solutions found by backtracking, including identical solutions. However `aggregate_all/4` and `aggregate/4` allow counting [in a way that disregards duplicates](http://www.swi-prolog.org/pldoc/man?section=aggregate). This might be worth a separate Question by you. – hardmath Dec 29 '13 at 13:35
13

For an ISO standard Prolog solution, you might use findall/3 to produce a list of all solutions, then set Count to the length of the resulting list. It could be a bit tricky to wrap this into a user-defined predicate count/2 as you propose, because we need to form the first argument of findall/3 in a way that accounts for any free (unbound) variables in the goal you want to pass as the first argument of count/2.

Many Prologs provide for "counters" or other forms of mutable global values, a nonstandard extension, that could be used in connection with a failure driven "loop" to make the same count. Slightly more cumbersome but sticking to the letter of the Prolog standard would be to use assert and retract to create your own "counter" by adjusting a dynamic fact.

An illustration of the latter approach follows. Making it "multithread safe" would require additional logic.

count(Goal,_) :-
    setGoalCount(0),
    call(Goal),
    incGoalCount(1),
    fail.              /* or false in some Prologs */
count(_,Count) :-
    getGoalCount(Count).

setGoalCount(_) :-
    retract(getGoalCount(_)),
    fail.
setGoalCount(X) :-
    assert(getGoalCount(X)).

incGoalCount(Y) :-
    retract(getGoalCount(X)),
    !,
    Z is X + Y,
    assert(getGoalCount(Z)).
hardmath
  • 8,753
  • 2
  • 37
  • 65
  • 3
    It's not about being "multithread safe". Simple nesting does not work `count(count((true;true),_),C).` should give 1, but you get 3. Also, ISO does not have `assert/1` but `asserta/1` and `assertz/1`. – false Nov 14 '12 at 20:22
  • 2
    Hi, yeah, reminds me of comp.lang.prolog thread we recently had! Would be also a business case for sys_make_surrogate/1 and setup_call_cleanup/3, if no findall/3 or "global variables" solution is desired. –  Aug 26 '14 at 21:26
9

count(P,Count) :-
        findall(1,P,L),
        length(L,Count).
尾崎隆大
  • 148
  • 1
  • 1
  • I can't find the library that contains the function `length()` or `findall()` – Tarek Oct 06 '13 at 22:48
  • @Tarek Both are built-in predicates and do not belong to any special library. You can use them directly, without any library import. – ThomasH Dec 26 '21 at 10:51