Prolog - count occurrence of number

Viewed 5191

I want to write predicate which can count all encountered number:

count(1, [1,0,0,1,0], X).
X = 2.

I tried to write it like:

count(_, [], 0).
count(Num, [H|T], X) :- count(Num, T, X1), Num = H, X is X1 + 1.

Why doesn't work it?

5 Answers

I have decided to add my solution to the list here.

Other solutions here use either explicit unification/failure to unify, or libraries/other functions, but mine uses cuts and implicit unification instead. Note my solution is similar to Ilario's solution but simplifies this using cuts.

count(_, [], 0) :- !.

count(Value, [Value|Tail],Occurrences) :- !, 
    count(Value,Tail,TailOcc), 
    Occurrences is TailOcc+1.

count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).

How does this work? And how did you code it?

It is often useful to equate solving a problem like this to solving a proof by induction, with a base case, and then a inductive step which shows how to reduce the problem down.

Line 1 - base case

Line 1 (count(_, [], 0) :- !.) handles the "base case". As we are working on a list, and have to look at each element, the simplest case is zero elements ([]). Therefore, we want a list with zero elements to have no instances of the Value we are looking for.

Note I have replaced Value in the final code with _ - this is because we do not care what value we are looking for if there are no values in the list anyway! Therefore, to avoid a singleton variable we negate it here.

I also added a ! (a cut) after this - as there is only one correct value for the number of occurrences we do not want Prolog to backtrack and fail - therefore we tell Prolog we found the correct value by adding this cut.

Lines 2/3 - inductive step

Lines 2 and 3 handle the "inductive step". This should handle if we have one or more elements in the list we are given. In Prolog we can only directly look at the head of the list, therefore let us look at one element at a time. Therefore, we have two cases - either the value at the head of the list is the Value we are looking for, or it is not.

Line 2

Line 2 (count(Value, [Value|Tail],Occurrences) :- !, count(Value,Tail,TailOcc), Occurrences is TailOcc+1.) handles if the head of our list and the value we are looking for match. Therefore, we simply use the same variable name so Prolog will unify them.

A cut is used as the first step in our solution (which makes each case mutually exclusive, and makes our solution last-call-optimised, by telling Prolog not to try any other rules).

Then, we find out how many instances of our term there are in the rest of the list (call it TailOcc). We don't know how many terms there are in the list we have at the moment, but we know it is one more than there are in the rest of the list (as we have a match).

Once we know how many instances there are in the rest of the list (call this Tail), we can take this value and add 1 to it, then return this as the last value in our count function (call this Occurences).

Line 3

Line 3 (count(Value, [_|Tail], Occurrences) :- count(Value,Tail,Occurrences).) handles if the head of our list and the value we are looking for do not match.

As we used a cut in line 2, this line will only be tried if line 2 fails (i.e. there is no match).

We simply take the number of instances in the rest of the list (the tail) and return this same value without editing it.

Related