Veins
Veins

Reputation: 59

Counting how many elements in a list of lists satisfy a predicate

Given a list of lists of integers, e.g. [[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]], I want to go over each sublist and count how many of them sum to 15. In this case that would be 1, for the sublist [3,10,2].

I am aware of the predicate aggregate_all/3, but I'm having trouble writing a predicate to check each element of the list, what I have now is something like

fifteens([X|Xs]) :-
    sum_list(X, 15),
    fifteens(Xs).

and within another predicate I have:

aggregate_all(count, fifteens(Combinations), Value).

where Combinations is the list of lists of integers in question.

I know my fifteens predicate is flawed since it's saying that all elements of the nested list must sum to 15, but to fix this how do I take out each element of Combinations and check those individually? Do I even need to? Thanks.

Upvotes: 0

Views: 316

Answers (2)

David Tonhofer
David Tonhofer

Reputation: 15316

This is a job for ... foldl/4. Functional programming idioms in logic programming languages? Yes, we can!

First, summing the summable values of a list:

sum_them(List,Sum) :-
   foldl(sum_goal,List,0,Sum).

sum_goal(Element,FromLeft,ToRight) :- 
   must_be(number,Element),
   must_be(number,FromLeft),
   ToRight is Element+FromLeft.

Then, counting the ones that sum to 15:

count_them(List,Count) :-
   foldl(count_goal,List,0,Count).

count_goal(Element,FromLeft,ToRight) :- 
   must_be(list(number),Element),
   must_be(number,FromLeft),
   sum_them(Element,15) -> succ(FromLeft,ToRight) ; FromLeft = ToRight.

Does it work? Let's write some unit tests:

:- begin_tests(fifteen_with_foldl).

test("first test",true(R==1)) :-
   count_them([[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]],R).

test("test on empty",true(R==0)) :-
   count_them([],R).

test("test with 2 hist",true(R==2)) :-
   count_them([[15],[],[1,1,1,1,1,10]],R).

:- end_tests(fifteen_with_foldl).

And so:

% PL-Unit: fifteen_with_foldl ... done
% All 3 tests passed
true.

Upvotes: 1

coder
coder

Reputation: 12972

First of all your fifteens/2 predicate has no because for empty list and thus it will always fails because due to the recursion eventually fifteens([]) will be called and fail.

Also you need to change completely the definition of fifteens, currently even if you add base case, it says check ALL elements-sublists to see if they sum to 15. That's Ok but I don't see how you could use it with aggregate.

To use aggregate/3 you need to express with fifteens/2, something like: for every part of my combinations list check separately each sublist i.e each member:

ifteens(L) :-
    member(X,L),
    sum_list(X, 15).

Now trying:

?- aggregate_all(count, ifteens([[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]]), Value).

Value = 1.

Upvotes: 2

Related Questions