6

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!

mat
  • 40,498
  • 3
  • 51
  • 78
Robert Hung
  • 165
  • 13
  • 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 May 06 '16 at 09:27

4 Answers4

3

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.
Ulukai
  • 183
  • 11
2

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.

Community
  • 1
  • 1
tas
  • 8,100
  • 3
  • 14
  • 22
  • 1
    A detail is actually a bit off... What about summing up **squares**? – repeat May 06 '16 at 08:49
  • 1
    @repeat: How embarrassing. It was right in front of my eyes. Thanks for the hint, I corrected my code. – tas May 06 '16 at 10:05
2

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.
repeat
  • 18,496
  • 4
  • 54
  • 166
0

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).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • 1
    There's no need to use if-then-else here. `C = 0` doesn't contribute anything to the sum! – repeat May 06 '16 at 08:59