CR7
CR7

Reputation: 53

grouping and summing items of list using java 8 streams

Suppose that I have a List<Banana> bananas. Where Banana class is defined like bellow :

Banana.java

public class Banana{
   String name;
   Long weight;
   Long price;
   // getters & setters
}

the bananas list contains :

[
   {"banana1", 10, 20}, 
   {"banana1", 10, 20}, 
   {"banana1", 10, 20},
   {"banana2", 20, 30},
   {"banana2", 30, 30},
   {"banana3", 50, 40},
]

what I want to do here is to group by banana's name where summing weight column & price column (each alone) so that I can have this result :

[
   {"banana1", 30, 60}, 
   {"banana2", 50, 60},
   {"banana3", 50, 40},
]

Thanks in advance.

Upvotes: 3

Views: 1573

Answers (3)

Hadi
Hadi

Reputation: 17289

Another solution is similar to this: I simplify solution by breaking it to three step.due to defined BiFunction and Function for it.

first function

this BiFunction get two Banana object and return single Banana object that summing expect properties.(price,weight)

 BiFunction<Banana, Banana, Banana> function =
            (o1, o2) -> new Banana(o2.getName(), o1.getWeight() +o2.getWeight(),
                                   o1.getPrice() + o2.getPrice());

and second function

this function get list of Banana object and return single Banana.

  Function<List<Banana>, Banana> function2 =
            l -> l.stream()
                  .reduce(new Banana("",0l,0l),(o1, o2) -> function.apply(o1, o2));

and finally

    List<Banana> bananaList = bananas.stream()
            .collect(Collectors.groupingBy(Banana::getName)).values()
            .stream()
            .map(function2::apply)
            .collect(Collectors.toList());

 List<Banana> bananaList = bananas.stream()
            .collect(Collectors.groupingBy(Banana::getName)).values()
            .stream()
            .map(bananas1 -> bananas1
                    .stream()
                    .reduce(new Banana("",0l,0l),
                            (banana1,banana2)->new Banana(banana2.getName(),
                                                 banana1.getWeight() + banana2.getWeight(),
                                                  banana1.getPrice() + banana2.getPrice())))
            .collect(Collectors.toList());

Upvotes: 0

Rann Lifshitz
Rann Lifshitz

Reputation: 4090

Following Eran's answer with a full, tested implementation via the java online compiler:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.Map;
import java.util.function.*;
import java.util.stream.*;
import java.util.StringJoiner;

public class Java8ForEachExample {

    public static void main(String[] args) {

        //creating sample Collection
        List<Banana> myList = new ArrayList<>();
        myList.add(new Banana("B1",5,30));
        myList.add(new Banana("B1",3,60));
        myList.add(new Banana("B2",1,30));
        myList.add(new Banana("B2",4,20));
        myList.add(new Banana("B2",7,100));
        // Eran's code here:
        Map<String,Banana> bananasByName =
        myList.stream()
           .collect(Collectors.toMap(Banana::getName,
                                     Function.identity(),
                                     (b1,b2)->{b1.addWeightAndPrice(b2); return b1;}));
        // End of Eran's code
        bananasByName.entrySet().forEach(e -> { 
           System.out.println(e.getKey() + " : " + e.getValue()); 
        });
    }

}

class Banana{
   private String name;
   public int weight;
   public int price;
   public Banana(String name, int weight, int price) {
       this.name = name;
       this.weight = weight;
       this.price = price;
   }
   public String getName() {
       return name;
   }
   public int getWeight() {
       return weight;
   }
   public int getPrice() {
       return price;
   }
   public void addWeightAndPrice(Banana other) {
       this.price +=other.getPrice();
       this.weight +=other.getWeight();
   }
   @Override
   public String toString() {
       return (new StringJoiner(", ")).add(name).add(Integer.toString(weight)).
          add(Integer.toString(price)).toString();
   }

}

Output:

B2 : B2, 12, 150
B1 : B1, 8, 90

Upvotes: 1

Eran
Eran

Reputation: 393781

You can group them with Collectors.toMap using the merge function:

Map<String,Banana> bananasByName =
    bananas.stream()
           .collect(Collectors.toMap(Banana::getName,
                                     Function.identity(),
                                     (b1,b2)->{ 
                                                 b1.addWeightAndPrice(b2); return b1; 
                                              }));

Where addWeightAndPrice is an instance method of the Banana class that adds the weight and price of the passed Banana to the Banana instance on which it is called.

If you don't want to mutate the original Banana instances, you can change the merge function to create a new Banana instance and add to it the weight and price of the two input Banana instances.

Upvotes: 6

Related Questions