Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using lambda expressions for summing up member variables?

I have a class like followings.

public class Votes{
    String name;
    int likes;
    int dislikes;

    //constructors, getters and setters    
}

I am having a list like followings.

List<Votes> votesList;

Assume I am populating above list with some elements. I want to declare a method which performs grouping and summing operation in that list.

As an example, assume I am giving the following elements in a list as the input for that method.

votesList.add(new Votes("A", 10, 5));
votesList.add(new Votes("B", 15, 10));
votesList.add(new Votes("A", 20, 15));
votesList.add(new Votes("B", 10, 25));
votesList.add(new Votes("C", 10, 20));
votesList.add(new Votes("C", 0, 15));

That method should output a List<Votes> with the following elements.

("A", 30, 20),
("B", 25, 35),
("C", 10, 35)

Is there an easy way to do that using streams, lambda expressions in Java8? I know how it can be done using collectors if I only have one int memeber.

Can someone please explain me how can I address this situation?

like image 326
Supun Wijerathne Avatar asked Oct 12 '16 14:10

Supun Wijerathne


People also ask

What is lambda expression in Java 8?

1 Java Lambda Expressions. Lambda Expressions were added in Java 8. A lambda expression is a short block of code which takes in parameters and returns a value. 2 Syntax. Expressions are limited. ... 3 Using Lambda Expressions. Lambda expressions can be stored in variables if the variable's type is an interface which has only one method.

How do I run a lambda expression in a variable?

Use Java's Consumer interface to store a lambda expression in a variable: To use a lambda expression in a method, the method should have a parameter with a single-method interface as its type. Calling the interface's method will run the lambda expression:

How do I Group on both properties of a lambda expression?

This code starts with a GroupBy clause with a Lambda expression. The Lambda expression (indicated with the => syntax in C# and the Function keyword in VB) uses the new keyword to create a new anonymous type with two properties: InvoiceDate and InvoiceType. This technique allows the code to group on both properties.

What is a variable capture in lambda expressions?

However, when a lambda expression uses a local variable from its enclosing scope, a special situation is created that is referred to as a variable capture. In this case, a lambda expression may only use local variables that are effectively final. An effectively final variable is one whose value does not change after it is first assigned.


3 Answers

One approach is to use groupingBy, combined with reducing:

Collection<Optional<Votes>> res =
     votesList.stream().collect(groupingBy(v -> v.name, reducing((v1, v2) -> new Votes(v1.name, v1.likes + v2.likes, v1.dislikes + v2.dislikes)))).values();

This will give you a Collection<Optional<Votes>>, you can get rid of it by combining the reducing collector and the finisher function Optional::get using the collectingAndThen collector but that will starts to look quite hard to read.

So another alternative could be to use toMap, and merge two votes whenever they have the same name:

Collection<Votes> res =
    votesList.stream().collect(toMap(v -> v.name,
                                     v -> v,
                                     (v1, v2) -> new Votes(v1.name, v1.likes + v2.likes, v1.dislikes + v2.dislikes))).values();

From there you can use the ArrayList copy-constructor (either by using collectingAndThen with m -> new ArrayList<>(m.values()), or by putting the previous expression in the parameter list of this copy constructor).

like image 114
Alexis C. Avatar answered Oct 13 '22 21:10

Alexis C.


Finally, I came across this as the easiest way. :))

Map<String, List<Votes>> grouped = voteCountList.stream().collect(Collectors.groupingBy(r->r.getName()));

List<Votes> collectedList = new ArrayList<>();

grouped.forEach((groupName, votes) -> collectedList.add(new Votes(groupName,
                votes.stream().collect(Collectors.summingInt(r->r.getLikes())),
                votes.stream().collect(Collectors.summingInt(r->r.getDislikes())))));
like image 42
Supun Wijerathne Avatar answered Oct 13 '22 21:10

Supun Wijerathne


This should get you started, it's not exactly what you want since it does not create the final list.

Map<String, Votes> collected = votesList.stream().collect(Collectors.groupingBy(Votes::getName,
        collectingAndThen(reducing(
                (originalVotes, newVotes) -> new Votes(originalVotes.getName(), originalVotes.getLikes() + newVotes.getLikes(), originalVotes.getDislikes() + newVotes.getDislikes()))
                , Optional::get)));

collected.forEach((key, value) -> {
    System.out.println(key + "," + value.getLikes() + "," + value.getDislikes());
});
like image 41
Geoffrey De Vylder Avatar answered Oct 13 '22 21:10

Geoffrey De Vylder