Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get the sum of given numbers in prolog?

I'm new to prolog and I'm doing some exercises for practice. So I'm trying to get the sum of the given numbers in a list. I'm trying to use this:

my_last(X, [X]).  
my_last(X, [_|L]) :- my_last(X, L).

(from here)

as my guide. So this is my code for getting the sum:

listsum(X, []).                  
listsum(X, [H|L]):-
    X is H + listsum(X, L).

when I compile it, it says

practice.pl:3: evaluable listsum(_G139,_G140) does not exist
practice.pl:2: Singleton variables: [X]

then when I try listsum(0, [1,2,3]). it returns false.

I still don't understand much about prolog, and list and recursion in prolog.

like image 780
Zik Avatar asked Jul 17 '12 10:07

Zik


People also ask

How do you find the sum in Prolog?

To sum the elements in a list inductively, we add the first element to the sum of the remaining ones. In Prolog we say this: sum([H|T], S) :- sum(T,X), S is H + X.

What is S () in Prolog?

It is Prolog-implementation specific. It refers to a successor-predicate, see this for some more info.


2 Answers

Arithmetic

As you already discovered, arithmetic can be handled in Prolog with the (is)/2 operator. It's because in Prolog, everything is only symbolic calculus: things don't have a meaning by default, so the unification (=)/2 wouldn't know that (+)/2 refers to the addition for example.

Now, your problem is that you use a regular predicate inside of (is)/2 (here it's your recursive call). Since (is)/2 only performs arithmetic, it doens't evaluate the predicate call. It doesn't even recognize it since it's not an arithmetic function.

The fix here would be to affect the result of the recursive call to a variable and then use it in the (is)/2 call:

listsum(X,[]).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.

Base case correctness

But if you test that code you will not get the desired result. The reason is that you have another problem, in your base case. The sum of the empty list isn't "anything", as you stated by writing

listsum(X,[]).

(X is a free variable, hence can be anything).

Instead, it's 0:

listsum(0, []).

The resulting code is:

listsum(0, []).
listsum(Result, [Head|Tail]) :-
    listsum(SumOfTail, Tail),
    Result is Head + SumOfTail.

Order of arguments

Now, as a sidenote, in Prolog a convention is that output variables should be put at the end of the predicate while input variables should be put at the start of the predicate, so to behave as wanted we could refactor as follows:

listsum([], 0).
listsum([Head|Tail], Result) :-
    listsum(Tail, SumOfTail),
    Result is Head + SumOfTail.

Tail Call Optimization

Now, we can still improve this predicate with more advanced techniques. For example we could introduce tail calls so that Tail Call Optimization (googlable) could be performed, thanks to an idiom of declarative programming called an accumulator:

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator is Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).

The idea behind that is to update an intermediate result at each step of the recursion (by adding the value of the current head of the list to it) and then just state that when the list is empty this intermediate value is the final value.

Getting more general programs

As you may have noted in Prolog, quite often predicates can be used in several ways. For example, length/2 can be used to discover the length of a list:

?- length([1, 2, 3], Length).
Length = 3.

or to build a skeleton list with free variables of a desired length:

?- length(List, 3).
List = [_G519, _G522, _G525].

Here though, you might have noted that you can't ask Prolog what are the lists which have a sum that is 6:

?- listsum(L, 6).
ERROR: is/2: Arguments are not sufficiently instantiated

That's because, to "go backwards", Prolog would have to solve an equation when comes the call to the (is)/2 operator. And while yours is simple (only additions), arithmetic isn't solvable this way in the general case.

To overcome that problem, constraint programming can be used. A very nice library is available for SWI, clpfd.

The syntax here would be:

:- use_module(library(clpfd)).

listsum(List, Sum) :-
    listsum(List, 0, Sum).

listsum([], Accumulator, Accumulator).

listsum([Head|Tail], Accumulator, Result) :-
    NewAccumulator #= Accumulator + Head,
    listsum(Tail, NewAccumulator, Result).

Now we can use our predicate in this other way we wished we could use it:

?- listsum(L, 6).
L = [6] ;
L = [_G1598, _G1601],
_G1598+_G1601#=6 ;
L = [_G1712, _G1715, _G1718],
_G1712+_G1715#=_G1728,
_G1728+_G1718#=6 . % Here I interrupted the answer but it would not terminate.

We could even ask for all the solutions to the problem:

?- listsum(L, X).
L = [],
X = 0 ;
L = [X],
X in inf..sup ;
L = [_G2649, _G2652],
_G2649+_G2652#=X . % Here I interrupted the answer but it would not terminate

I just mentionned that so that you realize that quite often the use of (is)/2 should be avoided and use of constraint programming should be preferred to get the most general programs.

like image 106
m09 Avatar answered Sep 30 '22 03:09

m09


If possible, use clpfd instead of plain old (is)/2 (and friends).

clpfd offers a logically pure predicate sum/3 that could fit your needs!

like image 25
repeat Avatar answered Sep 30 '22 03:09

repeat