I write this little code in Prolog that counts the number of occurrences of some term in a list. It works, but it is not tail recursive (so no recursive optimization).
How could I write the same program but with tail recursion?
counter(T,[],X) :-
X is 0.
counter(T,[T|D],X1) :-
!,
counter(T,D,X),
X1 is X+1.
counter(T,[_|D],X1) :-
counter(T,D,X1).
I think that I should use an accumulator but I've no idea how to implement. Any help?
Indentation is welcome even in Prolog, to make programs understandable:
counter(T, [], X) :-
X is 0.
counter(T, [T|D], X1) :-
!,
counter(T, D, X),
X1 is X+1.
counter(T, [_|D], X1) :-
counter(T, D, X1).
It is also a good practice to properly name your variables, here we could use:
counter(Elem, [], Result) :-
Result is 0.
counter(Elem, [Elem|Tail], Result) :-
!,
counter(Elem, Tail, NewResult),
Result is NewResult + 1.
counter(Elem, [_|Tail], Result) :-
counter(Elem, Tail, Result).
It's also a good practice to give a special name to singleton variables (prefixing them with a _
):
counter(_Elem, [], Result) :-
Result is 0.
counter(Elem, [Elem|Tail], Result) :-
!,
counter(Elem, Tail, NewResult),
Result is NewResult + 1.
counter(Elem, [_Head|Tail], Result) :-
counter(Elem, Tail, Result).
You can take advantage of the fact that Prolog uses unification in the heads of the clauses to rewrite your first clause:
counter(_Elem, [], Result) :-
Result is 0.
can become
counter(_Elem, [], 0).
Those clauses that consist only of a head are also called facts
The clause you have to change is the middle clause: the recursive call is not at the end of the predicate in it. Sadly it will impact the other clauses as well, let's see why.
To get tail recursion, we use an idiom called an accumulator: an additional argument that will hold intermediate results during the recursion. For example here:
counter(Elem, List, Result) :-
counter(Elem, List, 0, Result).
counter(_Elem, [], Acc, Acc).
counter(Elem, [Elem|Tail], Acc, Result) :-
!,
NewAcc is Acc + 1,
counter(Elem, Tail, NewAcc, Result).
counter(Elem, [_Head|Tail], Acc, Result) :-
counter(Elem, Tail, Acc, Result).
As you can see we have now a predicate counter/3
that only calls counter/4
and the latter tracks the intermediate result in the Acc
variable.
A problem remaining in your program is that you use is/2
. That doesn't give you a general program: you can't call counter(X, [1, 2, 3, 4], R)
and get an answer. To correct that you can use constraint programming:
:- use_module(library(clpfd)).
counter(Elem, List, Result) :-
counter(Elem, List, 0, Result).
counter(_Elem, [], Acc, Acc).
counter(Elem, [Elem|Tail], Acc, Result) :-
NewAcc #= Acc + 1,
counter(Elem, Tail, NewAcc, Result).
counter(Elem, [Head|Tail], Acc, Result) :-
Elem #\= Head,
counter(Elem, Tail, Acc, Result).
Test:
?- counter(X, [1, 2, 3, 4], R).
X = R, R = 1 ;
X = 2,
R = 1 ;
X = 3,
R = 1 ;
X = 4,
R = 1 ;
R = 0,
X in inf..0\/5..sup.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With