Reputation: 53
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
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
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
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