Robert Hung
Robert Hung

Reputation: 165

Creating a predicate in Prolog that sums the squares of only the even numbers in a list

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!

Upvotes: 6

Views: 2322

Answers (4)

tas
tas

Reputation: 8140

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.

Upvotes: 2

repeat
repeat

Reputation: 18726

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.

Upvotes: 2

CapelliC
CapelliC

Reputation: 60014

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).

Upvotes: 0

Ulukai
Ulukai

Reputation: 303

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.

Upvotes: 3

Related Questions