5
votes

I'm trying to figure out how to create a predicate in prolog that sums the squares of only the even numbers in a given list.

Expected output:

?- sumsq_even([1,3,5,2,-4,6,8,-7], Sum).

Sum = 120 ;

false.

What I know how to do is to remove all the odd numbers from a list:

sumsq_even([], []).
sumsq_even([Head | Tail], Sum) :-
    not(0 is Head mod 2),
    !,
    sumsq_even(Tail, Sum).
sumsq_even([Head | Tail], [Head | Sum]) :-  
    sumsq_even(Tail, Sum).

Which gives me:

Sum = [2, -4, 6, 8]

And I also know how to sum all the squares of the numbers in a list:

sumsq_even([], 0)
sumsq_even([Head | Tail], Sum) :-
    sumsq_even(Tail, Tail_Sum),
    Sum is Head * Head + Tail_Sum.

But I can't seem to figure out how to connect these two together. I'm thinking I may have gone the wrong way about it but I'm not sure how to define proper relationships to get it to make sense.

Thanks!

4
Ideally, your Prolog programs are pure relations. This means that they should be usable in all directions, also for example in the most general case. For instance, we would also like to obtain answers for ?- sumsq_even(Ls, Sum).. Check out the answers provided by @repeat and @tas for this generality. - mat

4 Answers

2
votes

Split your problem into smaller parts. As you already said, you have two different functionalities that should be combined:

  • remove odd numbers from a list (even)
  • sum all the squares of the numbers in a list (sumsq)

So, in the first place, use different predicate names for different functionalities:

even([], []).
even([Head | Tail], Sum) :-
    not(0 is Head mod 2),
    !,
    even(Tail, Sum).
even([Head | Tail], [Head | Sum]) :-  
    even(Tail, Sum).

sumsq([], 0).
sumsq([Head | Tail], Sum) :-
    sumsq(Tail, Tail_Sum),
    Sum is Head * Head + Tail_Sum.

In a third predicate you can now combine the two subsequent smaller steps:

sumsq_even(List, Sum) :-
    even(List, Even_List),
    sumsq(Even_List, Sum).

In this rule, first the (input) list is reduced to even elements (Even_List) and after that the sum of the squares are calculated.

This is the result for your example:

sumsq_even([1,3,5,2,-4,6,8,-7], Sum).
S = 120.
2
votes

Using and Prolog write:

:- use_module(library(clpfd)).
:- use_module(library(lambda)).

zs_sumevensq(Zs, S) :-
   maplist(\Z^X^(X #= Z*Z*(1-(Z mod 2))), Zs, Es),
   sum(Es, #=, S).

Sample query as given by the OP:

?- zs_sumevensq([1,3,5,2,-4,6,8,-7], S).
S = 120.
2
votes

You can actually do both tasks (filtering the even number and summing them up) at once:

:- use_module(library(clpfd)).

nums_evensumsq([],0).
nums_evensumsq([X|Xs],S0) :-
    X mod 2 #= 0,
    nums_evensumsq(Xs,S1),
    S0 #= S1 + X * X.
nums_evensumsq([X|Xs],S) :-
    X mod 2 #= 1,
    nums_evensumsq(Xs,S).

Querying the predicate gives the desired result:

   ?- nums_evensumsq([1,3,5,2,-4,6,8,-7],S).
S = 120 ? ;
no

You can write it even shorter using if_/3 as defined here:

nums_evensumsq([],0).
nums_evensumsq([X|Xs],S0) :-
    nums_evensumsq(Xs,S1),
    Y #= X mod 2,
    if_(Y = 0, S0 #= S1 + X * X, S0 #= S1).

Note that the comparison in the first argument of if_/3 is done with =/3 as defined here.

0
votes

Once you mastered the basics, you could be interested to learn about builtins. Library aggregate, provides a simple way to handle lists, using member/2 as list elements 'accessor':

sumsq_even(Ints, Sum) :-
  aggregate(sum(C), I^(member(I, Ints), (I mod 2 =:= 0 -> C is I*I ; C = 0)), Sum).